Make the C# TypeExpression handle every type kind - #11596
Timothee Guerin (timotheeguerin) merged 1 commit into
Conversation
commit: |
|
All changed packages have been documented.
Show changes
|
|
You can try these changes here
|
There was a problem hiding this comment.
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
TypeExpressioninstead of throwing. - Expanded
TypeExpressionto 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
isCSharpValueTypeutility 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.
1e8dbc8 to
3e407ef
Compare
…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.
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>
The C#
TypeExpressionthrew a hardErrorfor 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-csharphad to wrap every call site intry/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: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:
typescript-unsupported-scalar) from the C# components. C# now has its ownsrc/csharp/lib.ts, matching the existingtypescript/lib.tsandpython/lib.ts, which makes that class of mistake a type error.nullandneverintrinsics emittednullandnever, neither of which is a C# type. They now emitobjectandvoid.Also adds an
isCSharpValueTypeutil, 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
mainand can merge on its own.One of 7 PRs moving
@typespec/http-server-csharponto the emitter framework instead of its private forks: #11596, #11597, #11598, #11599, #11600, #11601, #11602.