Skip to content

feat(compiler): add extends base type clause for unions - #11771

Open
JoshLove-msft wants to merge 4 commits into
microsoft:mainfrom
JoshLove-msft:josh/union-extends
Open

feat(compiler): add extends base type clause for unions#11771
JoshLove-msft wants to merge 4 commits into
microsoft:mainfrom
JoshLove-msft:josh/union-extends

Conversation

@JoshLove-msft

@JoshLove-msft JoshLove-msft commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #2737

Adds experimental extends support to named union declarations.

Enable the feature in tspconfig.yaml to use it without an experimental warning:

features:
  - union-extends
model PetBase {
  name: string;
}
model Cat extends PetBase {
  toy: string;
}
model Dog extends PetBase {
  food: string;
}

union Pet extends PetBase {
  cat: Cat,
  dog: Dog,
}

Semantics

extends on a union is purely an assignability constraint:

  • Every variant must be assignable to the base type; a diagnostic is reported on any variant that is not.
  • The resolved base type is exposed on the type graph as Union.baseType, giving emitters an easy way to represent the union with a polymorphic base type in languages that do not support unions natively.
  • The base type does not become a variant of the union.
  • It does not create an inheritance relationship, make the union extensible, or interact with @discriminator.
  • Deprecation is deliberately not copied from the base type onto the union, unlike 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), extends remains 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

Area Change
Feature gate Adds the union-extends compiler feature and reports experimental-feature when it is not enabled
AST Adds UnionStatementNode.extends and narrows Union.baseType to Model | Scalar | Enum | Union
Parser/checker Parses the optional clause, resolves its data type, validates each variant, and detects circular base-type references
Type graph Navigates and mutates Union.baseType
Formatter Prints the heritage clause and uses shared empty-declaration comment handling
Tooling Updates TextMate grammar, LSP completion, compiler feature completion, the language spec, and docs

Circular references

union a extends a and alias indirection are caught by the existing pendingResolutions/ResolutionKind.BaseType mechanism.

Because a union constraint can also be a union expression, references such as union a extends a | string are detected on the resolved type. That walk deliberately follows only union expressions, so legal cyclic data graphs remain accepted:

model Box {
  inner: a;
}
union a extends Box {
  x: Box,
}

Templates

Variant assignability is skipped inside an uninstantiated template declaration. Each instantiation is checked, and each valid instantiation gets its own concrete baseType.

Validation

  • Full @typespec/compiler suite: 4,196 passed, 6 skipped
  • TypeScript build/typecheck: clean
  • Compiler lint and Prettier checks: clean

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
@pkg-pr-new

pkg-pr-new Bot commented Aug 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/compiler@11771

commit: 23cd0e9

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/compiler
Show changes

@typespec/compiler - feature ✏️

Add experimental support for an extends clause on union statements to constrain every variant to a common data type.,> ,> Enable the union-extends compiler feature in tspconfig.yaml to use the clause without an experimental feature warning.,> ,> tsp,> model PetBase {,> name: string;,> },> model Cat extends PetBase {,> toy: string;,> },> model Dog extends PetBase {,> food: string;,> },> ,> union Pet extends PetBase {,> cat: Cat,,> dog: Dog,,> },> ,> ,> The base type is exposed on the type graph as Union.baseType, giving emitters an easy way to know that all the variants of a union share a common base type. A diagnostic is reported on any variant that isn't assignable to the base type.,> ,> extends on a union is purely a constraint: it doesn't imply any subtyping relationship, it doesn't make the union extensible and it has no interaction with @discriminator.

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

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> { ... }, set Union.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.

Comment thread grammars/typespec.json
Comment thread packages/compiler/src/server/tmlanguage.ts
@azure-sdk-automation

azure-sdk-automation Bot commented Aug 27, 2026

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

Comment thread packages/compiler/src/formatter/print/comment-handler.ts Outdated

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 22 out of 22 changed files in this pull request and generated no new comments.

Comment thread packages/compiler/src/core/types.ts Outdated
Comment thread packages/compiler/src/core/parser.ts
Comment thread packages/compiler/src/core/checker.ts
Comment thread packages/compiler/src/server/tmlanguage.ts
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>
Copilot AI review requested due to automatic review settings August 28, 2026 21:45

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 26 out of 26 changed files in this pull request and generated 1 comment.


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
Copilot AI review requested due to automatic review settings August 28, 2026 21:54

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 26 out of 26 changed files in this pull request and generated no new comments.

注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

标签

compiler:core 问题 for @typespec/compiler meta:website TypeSpec.io updates

项目

None yet

Development

Successfully merging this pull request may close these issues.

Implementextends base type for union

4 participants