Skip to content

Make the C# TypeExpression handle every type kind - #11596

Merged
Timothee Guerin (timotheeguerin) merged 1 commit into
microsoft:mainfrom
timotheeguerin:ef/csharp-type-expression-total
Aug 28, 2026
Merged

Timothee Guerin (timotheeguerin) merged 1 commit into
microsoft:mainfrom
timotheeguerin:ef/csharp-type-expression-total

Conversation

@timotheeguerin

@timotheeguerin Timothee Guerin (timotheeguerin) commented Aug 7, 2026

Copy link
Copy Markdown
Member

The C# TypeExpression threw a hard Error for anything it did not recognise — tuples, string templates, enum members, template parameters and most of the intrinsics. An emitter hitting one of those got a crash with no source location instead of a diagnostic, so @typespec/http-server-csharp had to wrap every call site in try/catch.

Those kinds are now handled, and a genuinely unsupported type reports a diagnostic against the offending type and falls back to object, so compilation continues and the user gets a pointer to the spec that caused it. The diagnostics name the type, so it is actionable without bisecting the spec:

warning emitter-framework/csharp-unsupported-scalar: Scalar 'MyLib.ipAddress' has no
  C# equivalent, using 'object' instead. Extend a built-in scalar to control how it is emitted.

warning emitter-framework/csharp-unsupported-type: Type '@myLib.flag' of kind 'Decorator'
  is not supported in C#, using 'object' instead.

The two are kept separate because they call for different actions: the scalar one is fixable in the spec by extending a built-in scalar, while the other reports a gap in the emitter.

Two smaller bugs fixed along the way:

  • Unsupported scalars reported a TypeScript diagnostic (typescript-unsupported-scalar) from the C# components. C# now has its own src/csharp/lib.ts, matching the existing typescript/lib.ts and python/lib.ts, which makes that class of mistake a type error.
  • The null and never intrinsics emitted null and never, neither of which is a C# type. They now emit object and void.

Also adds an isCSharpValueType util, since deciding whether a TypeSpec type maps to a C# struct is something every C# emitter needs and every C# emitter was reimplementing.


Independent — targets main and can merge on its own.

One of 7 PRs moving @typespec/http-server-csharp onto the emitter framework instead of its private forks: #11596, #11597, #11598, #11599, #11600, #11601, #11602.

@pkg-pr-new

pkg-pr-new Bot commented Aug 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/emitter-framework@11596

commit: 3e407ef

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/emitter-framework
Show changes

@typespec/emitter-framework - fix ✏️

Make the C# TypeExpression handle every type kind instead of throwing,> ,> Tuple, StringTemplate, EnumMember, ModelProperty, UnionVariant, template parameters and the full Intrinsic set are now supported, and an unsupported type reports a diagnostic and falls back to object rather than throwing. The diagnostics now name the offending type instead of saying only "Unsupported scalar type":,> ,> ,> warning emitter-framework/csharp-unsupported-scalar: Scalar 'Currency' has no C# equivalent, using 'object' instead. Extend a built-in scalar to control how it is emitted.,> ,> ,> Also fixes the C# components reporting a TypeScript diagnostic for unsupported scalars, and corrects the C# expressions for the null and never intrinsics.

@azure-sdk-automation

azure-sdk-automation Bot commented Aug 7, 2026

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

@microsoft-github-policy-service microsoft-github-policy-service Bot added the stale Mark a PR that hasn't been recently updated and will be closed. label Aug 22, 2026
@timotheeguerin
Timothee Guerin (timotheeguerin) marked this pull request as ready for review August 27, 2026 16:36
Copilot AI lite review requested due to automatic review settings August 27, 2026 16:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the C# TypeExpression component in @typespec/emitter-framework so it no longer throws for previously-unhandled TypeSpec type kinds, instead reporting a diagnostic and falling back to object to keep emitting code.

Changes:

  • Added C#-specific diagnostics for unsupported scalars/types and used them in TypeExpression instead of throwing.
  • Expanded TypeExpression to handle additional TypeSpec kinds (e.g., tuples, string templates, enum members, template params, intrinsics) and corrected intrinsic mappings (null, never).
  • Introduced and exported a shared isCSharpValueType utility for determining whether a TypeSpec type maps to a C# struct.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/emitter-framework/src/lib.ts Registers new C# emitter-framework diagnostic codes used for graceful fallback behavior.
packages/emitter-framework/src/csharp/components/utils/value-type.ts Adds isCSharpValueType helper to centralize value-type vs reference-type decisions.
packages/emitter-framework/src/csharp/components/utils/index.ts Re-exports the new isCSharpValueType utility.
packages/emitter-framework/src/csharp/components/type-expression.tsx Refactors TypeExpression to be non-throwing, adds broader kind handling, and reports diagnostics with fallback types.
packages/emitter-framework/src/csharp/components/type-expression.test.tsx Adds tests covering newly-supported kinds and “fallback instead of throwing” behavior.
.chronus/changes/ef-csharp-type-expression-total-2026-8-5.md Adds a changelog entry describing the fix and behavior changes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Adds handling for Tuple, StringTemplate, EnumMember, ModelProperty, UnionVariant,
template parameters and the full Intrinsic set. An unsupported type now reports a
diagnostic and falls back to `object` instead of throwing.

Also fixes the C# components reporting a TypeScript diagnostic for unsupported
scalars, and corrects the C# expressions emitted for the `null` and `never`
intrinsics. Adds an `isCSharpValueType` util.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Merged via the queue into microsoft:main with commit bed13e3 Aug 28, 2026
33 checks passed
@timotheeguerin
Timothee Guerin (timotheeguerin) deleted the ef/csharp-type-expression-total branch August 28, 2026 17:11
Timothee Guerin (timotheeguerin) added a commit to timotheeguerin/typespec that referenced this pull request Aug 28, 2026
…crosoft#11597)

An override descriptor already accepted a `declaration` entry, but
nothing ever dispatched to it — only `reference` was wired up. So an
emitter could change how a type is *referenced* and had no say in how it
is *declared*. The only way to customize a declaration was to copy the
framework component into your own package and diverge from it, which is
exactly what `@typespec/http-server-csharp` did for classes, properties
and enums.

`Experimental_OverridableComponent` now dispatches `declaration` too,
and the C# `ClassDeclaration`, `Property` and `EnumDeclaration` render
through it. An override can replace the declaration outright, or
re-render the default with different props:

```tsx
const overrides = Experimental_ComponentOverridesConfig().forTypeKind("ModelProperty", {
  declaration: (props) =>
    isSecret(props.type) ? (
      <props.Declaration {...props.declarationProps} public={false} internal />
    ) : (
      props.default
    ),
});
```

`props.Declaration` is the unwrapped body, so re-rendering it does not
recurse back through the override.

The declaration props type is generic and defaults to `Record<string,
any>`, because the existing `Experimental_CustomTypeToProps` map is
TypeScript-specific (`VarDeclarationProps`, `ObjectPropertyProps`) and
means nothing for C#. Callers that want type safety pass the concrete
props type: `.forTypeKind<"ModelProperty", PropertyProps>(...)`.

---

Independent — targets `main` and can merge on its own.

One of 7 PRs moving `@typespec/http-server-csharp` onto the emitter
framework instead of its private forks: microsoft#11596, microsoft#11597, microsoft#11598, microsoft#11599,
microsoft#11600, microsoft#11601, microsoft#11602.
Timothee Guerin (timotheeguerin) added a commit to timotheeguerin/typespec that referenced this pull request Aug 28, 2026
The emitter carried a 217-line hand-maintained table of C# keywords and
a `createLibrary` call re-declaring the `System.Text.Json.Serialization`
attributes. `@alloy-js/csharp` ships both, and its versions are better:
the local keyword list was missing `delegate`, and hand-declared symbols
do not participate in Alloy's automatic `using` management.

Both are deleted in favour of the Alloy equivalents
(`isValidCSharpIdentifier`, `csharpKeywords`, the `System/Text/Json`
builtins), along with the local `getDocComments` copy, which is already
exported from `@typespec/emitter-framework/csharp`.

One thing Alloy deliberately does *not* do is worth calling out: its
name policy `@`-escapes real keywords, but the emitter needs namespace
segments colliding with common BCL type names to be **renamed** — a
namespace called `Type` shadows `System.Type` and breaks every
`typeof()` in the generated converters. That rule survives, now isolated
in `getCSharpNamespaceName` and built on Alloy's keyword sets rather
than a parallel list.

Generated output is unchanged.

---

Independent — targets `main` and can merge on its own.

One of 7 PRs moving `@typespec/http-server-csharp` onto the emitter
framework instead of its private forks: microsoft#11596, microsoft#11597, microsoft#11598, microsoft#11599,
microsoft#11600, microsoft#11601, microsoft#11602.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter-framework Issues for the emitter framework stale Mark a PR that hasn't been recently updated and will be closed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants