Repository navigation
Build models, properties and enums on the emitter framework - #11601
Timothee Guerin (timotheeguerin) wants to merge 3 commits into
Conversation
commit: |
|
All changed packages have been documented.
Show changes
|
|
You can try these changes here
|
1befaa3 to
7a89953
Compare
c734510 to
1f31415
Compare
ClassDeclaration takes an explicit property list and extra members, Property takes the full Alloy property prop set plus name/csharpType overrides, EnumDeclaration takes an explicit member list and jsonAttributes, and JsonConverter takes doc, access modifiers, extra members and an explicit csharpType.
…ource of truth The emitter had three disagreeing scalar-to-C# name maps, so error model constructors declared parameters such as DateOnly, Uri and sbyte while the matching properties were DateTime, string and SByte - code that does not compile. NumericConstraintAttribute<T> had the same mismatch.
…he emitter framework Replaces the private forks of ClassDeclaration, Property and EnumDeclaration with the framework components, expressing the emitter's own behavior as a declaration override and component props. Generated output is unchanged.
…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# `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: microsoft#11596, microsoft#11597, microsoft#11598, microsoft#11599, microsoft#11600, microsoft#11601, microsoft#11602.
1f31415 to
33e11c2
Compare
|
Hi @Timothee Guerin (@timotheeguerin). The PR will be closed since the PR has no update for 28 days. If this is still relevant please reopen. |
The emitter shipped private forks of the framework's
ClassDeclaration,PropertyandEnumDeclaration— copies made because the framework offered no way to adjust them, which then drifted. With declaration overrides and component props now available (#11597, #11598), the forks go away and the divergences are expressed as configuration:virtual/override/new, error-model members, JSON attributes) becomes a registeredModelPropertydeclaration override.ServerClassDeclarationbecomesEfClassDeclarationwith an explicitpropertieslist and the generated constructors passed aschildren.Enumsrenders throughEfEnumDeclarationwith an explicitmemberslist, keeping the emitter's more permissive union-as-enum detection.models.tsxdrops from ~270 lines to ~125, and the whole change is a net -350 lines.One structural note for reviewers: the framework's
TypeExpressionrecurses into itself, not into a consumer's wrapper. Any type kind whose handling recurses into a contained type therefore has to stay in the emitter's wrapper or its divergences are silently lost for the nested type. Scalars are the exception — they go through component overrides, which apply at every level. This is now written down in a comment at the top of the wrapper, because it is not obvious and it caused a regression while writing this.Generated output is unchanged.
This is the tip of the series and is still blocked on #11598 and #11600, whose commits it currently carries. A PR can only declare one base, so this stays on
mainand will reduce to a single commit as they merge.One of 7 PRs moving
@typespec/http-server-csharponto the emitter framework instead of its private forks. Merged: #11596, #11597, #11599. Remaining: #11598, #11600, #11601, #11602.