Skip to content

feat(compiler): allow inline decorators on declaration expressions#11035

Draft
timotheeguerin wants to merge 13 commits into
microsoft:mainfrom
timotheeguerin:decl-expr-inline-decorators
Draft

feat(compiler): allow inline decorators on declaration expressions#11035
timotheeguerin wants to merge 13 commits into
microsoft:mainfrom
timotheeguerin:decl-expr-inline-decorators

Conversation

@timotheeguerin

Copy link
Copy Markdown
Member

Note

Stacked on top of #11019 (declarations-as-expressions). This PR targets main, so its diff currently includes the decl-expr commits as well — it should be reviewed/merged after #11019, or rebased once that lands.

Summary

Builds on the declaration-expressions feature to allow decorators to be applied inline to model, enum, union, and scalar declarations used in expression position.

model Foo {
  status: @doc("the current status") enum { active, inactive };
  inner: @doc("nested model") model Inner { x: string };
}

Changes (incremental over #11019)

  • Parser (parser.ts): the @ case in parsePrimaryExpression now parses a decorator list and, when followed by a model/enum/union/scalar keyword, dispatches to the corresponding parse*Statement (with pos captured at @ so the node span includes the decorators). Decorators before any other expression still report invalid-decorator-location ("Cannot decorate expression."). No checker changes were required — parse*Statement already plumbs decorators onto the AST node and the checker already applies them.
  • Formatter (printer.ts): the four print*Statement printers now pass tryInline: isInExpressionPosition(path), keeping decorators on the same line in expression position while statement-position decorators still break to their own line. Output is idempotent.
  • Grammar spec (spec.emu.html): added the optional DecoratorList? prefix to the four *DeclarationExpression productions.

Tests

  • Checker tests verifying the decorator is applied (via getDoc) for anonymous enum, named model, keyword union, and scalar declaration expressions, plus a negative test that non-declaration expressions still reject decorators.
  • Formatter tests covering inline formatting for each kind.

Validation

  • @typespec/compiler builds clean
  • declaration-expressions.test.ts and formatter.test.ts pass (254 tests)
  • Prettier + oxlint clean on changed files

Allow model, enum, union, and scalar declarations to be used in
expression position (e.g. alias RHS, property types). In expression
position they are anonymous (name is "") and the resulting type has
expression: true; they are not registered in the enclosing namespace.

A diagnostic is reported when template parameters are used on a
declaration in expression position.
A keyword-form union (`union { a, b }`) used in expression position is marked
`expression: true`, which caused checkUnionExpression to flatten its (possibly
named) variants into the parent union, silently dropping colliding members.
Flatten only unions originating from the `|` operator (UnionExpression node).
Add tests for:
- expression: false on statement declarations
- name retention on named declaration expressions
- named expressions not being referenceable
- union namespace non-registration
- alias-resolved types, op return/param, union variant usage
- member access via alias, decorator rejection
- enum values, union named variants, scalar constructors, model spread
- parser negatives for interface/op in expression position
- formatter named & nested declaration expressions
Anonymous declarations used in expression position rendered with a stray
namespace prefix (e.g. `Ns.` for enum/scalar, `Ns.{ x: string }` for
keyword-form model). Render them inline and un-prefixed, mirroring union
expression naming.

Also extract a single shared `isDeclarationInExpressionPosition` helper used
by both the binder and checker so the two position predicates cannot drift,
and add regression tests (type names, keyword-form union as `|` operand,
template parameter referenced inside an expression declaration).
Inline anonymous declaration expressions and hoist named ones across the
OpenAPI and JSON Schema emitters, validate keyword-form union expression
variants in versioning, and derive the enum typekit `expression` flag from
an empty name.
@pkg-pr-new

pkg-pr-new Bot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@typespec/compiler

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

@typespec/html-program-viewer

npm i https://pkg.pr.new/@typespec/html-program-viewer@11035

@typespec/json-schema

npm i https://pkg.pr.new/@typespec/json-schema@11035

@typespec/openapi

npm i https://pkg.pr.new/@typespec/openapi@11035

@typespec/openapi3

npm i https://pkg.pr.new/@typespec/openapi3@11035

@typespec/versioning

npm i https://pkg.pr.new/@typespec/versioning@11035

commit: 3de49f4

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/compiler
  • @typespec/html-program-viewer
  • @typespec/json-schema
  • @typespec/openapi
  • @typespec/openapi3
  • @typespec/versioning
Show changes

@typespec/compiler - feature ✏️

Allow augment decorators (@@) to target model, enum, union, and scalar declarations used in expression position (reached via a navigation reference such as ::type).,> ,> tsp,> model Foo {,> status: enum { active, inactive };,> },> ,> @@doc(Foo.status::type, "the current status");,>

@typespec/compiler - feature ✏️

Allow decorators to be applied inline to model, enum, union, and scalar declarations used in expression position.,> ,> tsp,> model Foo {,> status: @doc("the current status") enum { active, inactive };,> inner: @doc("nested model") model Inner { x: string };,> },>

@typespec/json-schema - feature ✏️

Support model, enum, union, and scalar declarations used in expression position. Anonymous declaration expressions are inlined, while named ones are hoisted into their own schema.,> ,> tsp,> model Foo {,> status: enum { active, inactive }; // inlined,> unit: scalar extends string; // inlined,> inner: model Inner { x: string }; // hoisted as `Inner.json`,> },>

@typespec/openapi - feature ✏️

Support model, enum, union, and scalar declarations used in expression position. Anonymous declaration expressions are inlined, while named ones are hoisted into a referenced component.,> ,> tsp,> model Foo {,> status: enum { active, inactive }; // inlined,> unit: scalar extends string; // inlined,> inner: model Inner { x: string }; // hoisted as component `Inner`,> },>

@typespec/openapi3 - feature ✏️

Support model, enum, union, and scalar declarations used in expression position. Anonymous declaration expressions are inlined, while named ones are hoisted into a referenced component.,> ,> tsp,> model Foo {,> status: enum { active, inactive }; // inlined,> unit: scalar extends string; // inlined,> inner: model Inner { x: string }; // hoisted as component `Inner`,> },>

@typespec/compiler - feature ✏️

$.enum.create now produces an enum expression (expression: true) when given an empty name, mirroring $.model.create.

@typespec/versioning - feature ✏️

Validate the variants of a keyword-form union expression (union { ... }) used in expression position like the variants of a named union, so versioning incompatibilities on decorated variants are reported.

@typespec/compiler - feature ✏️

Allow model, enum, union, and scalar declarations to be used as expressions. A declaration used in expression position has its corresponding type marked with expression: true and is not registered in the enclosing namespace. It may be named or anonymous (in which case its name is "").,> ,> tsp,> alias Foo = enum {,> a,,> b,,> };,> ,> model Bar {,> status: enum { active, inactive };,> unit: scalar extends string;,> inner: model Inner { x: string };,> },>

@typespec/html-program-viewer - feature ✏️

Display the new expression property on Model, Enum, and Scalar types in the program viewer.

@azure-sdk-automation

azure-sdk-automation Bot commented Jun 22, 2026

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

@timotheeguerin timotheeguerin force-pushed the decl-expr-inline-decorators branch from 5894e7b to 24d23ff Compare June 22, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant