feat(compiler): add extends base type clause for unions - #11771
feat(compiler): add extends base type clause for unions#11771JoshLove-msft wants to merge 4 commits into
extends base type clause for unions#11771Conversation
A named union can now declare a base type with `extends`. Every variant must be assignable to that base type, and the resolved type is exposed on the type graph as `Union.baseType` so emitters can represent the union with a polymorphic base type in languages without native unions. `extends` on a union is purely a constraint: it doesn't create any inheritance relationship, the base type doesn't become a variant, it doesn't make the union extensible and it has no interaction with `@discriminator`. Fixes microsoft#2737 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: be5c2e95-cfc7-417c-bc70-b34cf66bbea4
commit: |
|
All changed packages have been documented.
Show changes
|
There was a problem hiding this comment.
Pull request overview
Adds extends support to named union declarations as an assignability constraint (exposed as Union.baseType), enabling better authoring validation and simpler emitter modeling of unions via a polymorphic base type.
Changes:
- Compiler: parse/check
union <Name> extends <Expression> { ... }, setUnion.baseType, validate each variant is assignable, and detect circular base-type references. - Tooling/UX: update LSP completion, TextMate grammars/colorization, semantic-walker navigation, and experimental mutator graph support.
- Docs/tests: document the feature and add broad test coverage across parser/checker/formatter/tooling.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| website/src/content/docs/docs/language-basics/unions.md | Documents union extends as a constraint and clarifies semantics for users/emitters. |
| packages/spec/src/spec.emu.html | Updates the published grammar to include an optional UnionExtends. |
| packages/compiler/test/server/completion.test.ts | Adds keyword + identifier completion coverage for union extends. |
| packages/compiler/test/server/colorization.test.ts | Adds tokenization tests for unions with extends (incl. templated unions). |
| packages/compiler/test/semantic-walker.test.ts | Ensures semantic navigation traverses Union.baseType. |
| packages/compiler/test/parser.test.ts | Extends parser roundtrip/error coverage for union extends syntax. |
| packages/compiler/test/formatter/scenarios/outputs/union.tsp | Updates formatter scenario outputs for unions with extends + empty-body comment case. |
| packages/compiler/test/formatter/scenarios/inputs/union.tsp | Adds formatter inputs for unions with extends and comment preservation. |
| packages/compiler/test/formatter/formatter.test.ts | Adds unit tests validating formatting of union extends clauses and comment behavior. |
| packages/compiler/test/experimental/mutator.test.ts | Verifies global graph mutation includes Union.baseType. |
| packages/compiler/test/checker/union.test.ts | Adds comprehensive semantics tests for baseType setting, diagnostics, templates, cycles, and deprecation behavior. |
| packages/compiler/src/server/tmlanguage.ts | Adds TextMate rules for union extends highlighting. |
| packages/compiler/src/server/completion.ts | Enables extends keyword completion in union headers. |
| packages/compiler/src/formatter/print/printer.ts | Prints union heritage clause and preserves dangling comments in empty unions. |
| packages/compiler/src/formatter/print/comment-handler.ts | Adds comment handling to attach empty-union comments correctly. |
| packages/compiler/src/experimental/mutators.ts | Mutates Union.baseType as part of union graph mutation. |
| packages/compiler/src/core/types.ts | Adds Union.baseType and UnionStatementNode.extends to core type/AST definitions. |
| packages/compiler/src/core/semantic-walker.ts | Navigates Union.baseType during semantic walking. |
| packages/compiler/src/core/parser.ts | Parses optional union extends clause and includes it in AST traversal. |
| packages/compiler/src/core/checker.ts | Checks/sets Union.baseType, validates variants against it, and handles circular-reference detection. |
| grammars/typespec.json | Updates JSON TextMate grammar with union-extends. |
| .chronus/changes/union-extends-base-type-2026-8-26.md | Adds changelog entry for the new compiler feature. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
You can try these changes here
|
Gate union extends behind an experimental compiler feature, restrict base types to data declarations, reject model expressions, and consolidate empty-declaration comment handling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
|
||
| The base type does **not** become a variant of the union. `Breed` above still has exactly two variants. | ||
|
|
||
| `extends` is a constraint, not a declaration of inheritance. A variant only needs to be assignable to the base type, it doesn't have to explicitly `extends` it: |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4518d421-bd02-4083-abc6-c5bffa94918e
Fixes #2737
Adds experimental
extendssupport to named union declarations.Enable the feature in
tspconfig.yamlto use it without an experimental warning:Semantics
extendson a union is purely an assignability constraint:Union.baseType, giving emitters an easy way to represent the union with a polymorphic base type in languages that do not support unions natively.@discriminator.scalar extends.The base expression must resolve to a model, scalar, enum, or union. Union, intersection, array, and template expressions are supported when they resolve to one of those data types. Anonymous model expressions are rejected, including through aliases.
Per #2737 (comment),
extendsremains the constraint keyword because template constraints already use it for structural assignability. Nominal typing and extensible unions remain separate concerns in #3900 and #3901.Implementation
union-extendscompiler feature and reportsexperimental-featurewhen it is not enabledUnionStatementNode.extendsand narrowsUnion.baseTypetoModel | Scalar | Enum | UnionUnion.baseTypeCircular references
union a extends aand alias indirection are caught by the existingpendingResolutions/ResolutionKind.BaseTypemechanism.Because a union constraint can also be a union expression, references such as
union a extends a | stringare detected on the resolved type. That walk deliberately follows only union expressions, so legal cyclic data graphs remain accepted:Templates
Variant assignability is skipped inside an uninstantiated template declaration. Each instantiation is checked, and each valid instantiation gets its own concrete
baseType.Validation
@typespec/compilersuite: 4,196 passed, 6 skipped