Skip to content

[compiler] Add experimental typeref values - #12461

Open
Chenjie Shi (tadelesh) wants to merge 16 commits into
microsoft:mainfrom
tadelesh:typeref-value
Open

Chenjie Shi (tadelesh) wants to merge 16 commits into
microsoft:mainfrom
tadelesh:typeref-value

Conversation

@tadelesh

@tadelesh Chenjie Shi (tadelesh) commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Add typeref(Type) for embedding references to compiler types in metadata values, using native, cached scalar TypeRef<T = unknown> storage and TypeSpec assignability checks.
  • Gate function calls with the default-off typeref compiler feature in the calling project or library. Export TypeRefType, TypeRefValue, and isTypeRefType from @typespec/compiler; there is no TypeRef-specific experimental subpackage.
  • Map native reference storage and metadata reference values through mutation, preserving the storage annotation separately from the precise target. Include reference-containing decorator values, defaults, scalar constructor arguments, and template arguments.
  • Treat references as compilation metadata rather than runtime JSON values, with TypeRef-specific schema handling.

Opt in

kind: project
features:
  - typeref
model Item { id: string; }
model Registration { name: string; type: TypeRef; }
const entry: Registration = #{ name: "item", type: typeref(Item) };

The standard-library function declaration retains a declaration-local #suppress "experimental-feature" for the separate function-declaration warning. Function use is controlled by the typeref feature flag.

Implementation scope

TypeRef is not a new intrinsic type kind or runtime intrinsic scalar. Its factory resolves the native declaration and uses the compiler's existing instantiation cache. TypeRef-specific assignability remains in areScalarsRelated.

Mutation adds direct TypeRef handling while preserving the existing traversal helpers and lazy-clone behavior. Native storage uses the canonical factory; reference values map both their original annotation and precise target. Reference-containing template mappers keep args, map, and getMappedType consistent, including mixed type/value arguments.

The pre-existing lazy-owner and recursive lazy-clone issues are explicitly outside this PR's scope; this does not claim to fix those graphs. See #7003.

Existing handling of non-representable examples, defaults, and extension values is unchanged. General serialization-error recovery, example filtering, and first-serializable-example selection are outside this PR's scope.

Emitter integration

  • GraphQL: diagnose references in emitted fields, root operations, and attached output-object operations, including external @operationFields resolvers. Ignore operations and metadata models that are not rendered.
  • OpenAPI 3: inline native reference types as schemas accepting no JSON values. Existing example serialization is unchanged.
  • JSON Schema: keep native reference schemas inline and represent them as accepting no JSON values. Existing default, example, and extension serialization is unchanged.

Related to #6322.

Validation

Full workspace build: 48 successful tasks, followed by engineering checks and compiler signature regeneration. The complete test suite passed: 10,678 tests passed and 25 skipped. Full formatting, format verification, and strict type-aware lint passed. Spelling checks passed for all 39 files in the TypeRef diff.

Introduce typeref as an experimental function returning TypeRefValue metadata with unknown storage type. Preserve compiler type identity through nested values, JavaScript calls, and mutation, and reject JSON serialization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d925ae2-0253-4c4d-a87b-49740d9ef737
@pkg-pr-new

pkg-pr-new Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@typespec/compiler

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

@typespec/graphql

npm i https://pkg.pr.new/@typespec/graphql@12461

@typespec/json-schema

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

@typespec/openapi3

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

@typespec/tspd

npm i https://pkg.pr.new/@typespec/tspd@12461

commit: bba3f02

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • ✅ @typespec/compiler
  • ✅ @typespec/graphql
  • ✅ @typespec/json-schema
  • ✅ @typespec/openapi3
  • ✅ @typespec/tspd
Show changes

@typespec/compiler - fix ✏️

Correct reflection type constraint comparisons in function signatures, template parameters, and type references so namespace and other non-model constraints are not accepted as model constraints.

@typespec/compiler - feature ✏️

Add typeref(Type) for referencing types from metadata values. Enable the typeref compiler feature in the calling project or library's tspconfig.yaml. Its result has type TypeRef<T>, where T is the referenced type. Use TypeRef to accept any type reference, or TypeRef<T> to constrain its target. JavaScript implementations and generated signatures use TypeRefValue from @typespec/compiler, whose target is the referenced compiler type. Type references are compilation metadata and cannot be represented as JSON values.,> ,> typespec,> model Item { id: string; },> model Registration { name: string; type: TypeRef; },> const entry: Registration = #{ name: "item", type: typeref(Item) };,>

@typespec/tspd - feature ✏️

Add typeref(Type) for referencing types from metadata values. Enable the typeref compiler feature in the calling project or library's tspconfig.yaml. Its result has type TypeRef<T>, where T is the referenced type. Use TypeRef to accept any type reference, or TypeRef<T> to constrain its target. JavaScript implementations and generated signatures use TypeRefValue from @typespec/compiler, whose target is the referenced compiler type. Type references are compilation metadata and cannot be represented as JSON values.,> ,> typespec,> model Item { id: string; },> model Registration { name: string; type: TypeRef; },> const entry: Registration = #{ name: "item", type: typeref(Item) };,>

@typespec/graphql - feature ✏️

Add typeref(Type) for referencing types from metadata values. Enable the typeref compiler feature in the calling project or library's tspconfig.yaml. Its result has type TypeRef<T>, where T is the referenced type. Use TypeRef to accept any type reference, or TypeRef<T> to constrain its target. JavaScript implementations and generated signatures use TypeRefValue from @typespec/compiler, whose target is the referenced compiler type. Type references are compilation metadata and cannot be represented as JSON values.,> ,> typespec,> model Item { id: string; },> model Registration { name: string; type: TypeRef; },> const entry: Registration = #{ name: "item", type: typeref(Item) };,>

@typespec/json-schema - feature ✏️

Add typeref(Type) for referencing types from metadata values. Enable the typeref compiler feature in the calling project or library's tspconfig.yaml. Its result has type TypeRef<T>, where T is the referenced type. Use TypeRef to accept any type reference, or TypeRef<T> to constrain its target. JavaScript implementations and generated signatures use TypeRefValue from @typespec/compiler, whose target is the referenced compiler type. Type references are compilation metadata and cannot be represented as JSON values.,> ,> typespec,> model Item { id: string; },> model Registration { name: string; type: TypeRef; },> const entry: Registration = #{ name: "item", type: typeref(Item) };,>

@typespec/openapi3 - feature ✏️

Add typeref(Type) for referencing types from metadata values. Enable the typeref compiler feature in the calling project or library's tspconfig.yaml. Its result has type TypeRef<T>, where T is the referenced type. Use TypeRef to accept any type reference, or TypeRef<T> to constrain its target. JavaScript implementations and generated signatures use TypeRefValue from @typespec/compiler, whose target is the referenced compiler type. Type references are compilation metadata and cannot be represented as JSON values.,> ,> typespec,> model Item { id: string; },> model Registration { name: string; type: TypeRef; },> const entry: Registration = #{ name: "item", type: typeref(Item) };,>

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.

🟡 Changes recommended

Type references remain pointed at original types after mutation in scalar constructor arguments, template arguments, and decorators on lazily cloned models.

3 open issues
What changed in this PR

Adds experimental typeref(Type) values so TypeSpec libraries can embed compiler-type references in metadata without introducing a new intrinsic type.

Changes:

  • Introduces TypeRefValue, backed by unknown, with the referenced type in .target.
  • Integrates references with value handling, mutation, diagnostics previews, and explicit JSON-serialization rejection.
  • Adds regression tests, language documentation, and a feature changelog.
File Description
website/​src/​content/​docs/​docs/​language-basics/​values.md Documents type-reference syntax, validation, and limitations.
packages/​compiler/​test/​server/​completion.test.ts Updates completion expectations for typeref.
packages/​compiler/​test/​experimental/​typeref.test.ts Tests identity, nesting, function round trips, and invalid inputs.
packages/​compiler/​test/​experimental/​mutator.test.ts Tests reference mutation in defaults and decorator values.
packages/​compiler/​test/​decorators/​examples.test.ts Tests JSON-serialization rejection.
packages/​compiler/​src/​lib/​tsp-index.ts Registers the built-in function implementation.
packages/​compiler/​src/​lib/​examples.ts Rejects type references during JSON serialization.
packages/​compiler/​src/​index.ts Exports TypeRefValue.
packages/​compiler/​src/​experimental/​typeref.ts Creates opaque type-reference values.
packages/​compiler/​src/​experimental/​mutators.ts Traverses defaults and remaps reference targets.
packages/​compiler/​src/​core/​types.ts Defines the new value kind.
packages/​compiler/​src/​core/​scanner.ts Removes typeref from reserved keywords.
packages/​compiler/​src/​core/​js-marshaller.ts Passes reference wrappers to JavaScript unchanged.
packages/​compiler/​src/​core/​helpers/​type-name-utils.ts Formats reference previews.
packages/​compiler/​src/​core/​checker.ts Preserves references during value handling.
packages/​compiler/​lib/​std/​types.tsp Declares the experimental standard-library function.
packages/​compiler/​generated-defs/​TypeSpec.ts Adds generated function signatures.
.chronus/​changes/​typeref-values.md Records the feature with a usage example.

Review effort: Balanced

@azure-sdk-automation

azure-sdk-automation Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

Traverse scalar constructor and template value arguments, preserve remapped metadata on lazily cloned owners, and keep template-map accessors consistent with remapped values. Add focused regression coverage for the three verified Copilot review findings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d925ae2-0253-4c4d-a87b-49740d9ef737
Copilot AI balanced review requested due to automatic review settings November 2, 2026 09:53

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.

🔵 Needs a closer look

Filtered-default traversal fails to propagate a lazily cloned property into the cloned model, leaving a reference to unmutated Item and requiring a fix with regression coverage.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Propagate cloned properties into lazily cloned models

packages/​compiler/​src/​experimental/​mutators.ts:724

With Model.filter: m => m.name === "Item" and model Foo { itemType: unknown = typeref(Item); }, this traversal lazily clones both Foo and its property, but mutateSubMap still uses the original mutating === false (mutators.ts:916–920), so the returned model retains the original property and its reference to unmutated Item. Please propagate the cloned property into the lazily cloned model using the live mutation state, and add a regression covering this filtered-default case.

3 resolved since last review

Review effort: Balanced

Comment thread packages/compiler/lib/std/types.tsp Resolved
Comment thread packages/compiler/src/core/types.ts Resolved
Comment thread packages/compiler/src/core/checker.ts Resolved
Use native cached TypeRef<T> scalars to check reference targets and reject ordinary values. Address feature gating, property documentation, Vitest expectations, lazy-clone propagation, generated signatures, and schema emitter integration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d925ae2-0253-4c4d-a87b-49740d9ef737
Copilot AI balanced review requested due to automatic review settings November 3, 2026 03:13
@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

Fiona Huang (@FionaBronwen) Swati Kumar (@swatkatz) Steve Rice (@steverice) — this PR modifies files in the emitter:graphql area, which your team owns. Please take a look. (You can't be added as a formal reviewer because you're not a repository collaborator, so this is a heads-up instead.)

@microsoft-github-policy-service microsoft-github-policy-service Bot added emitter:graphql Issues for @typespec/graphql emitter tspd Issues for the tspd tool emitter:json-schema emitter:openapi3 Issues for @typespec/openapi3 emitter labels Nov 3, 2026

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.

🟡 Changes recommended

Reachable TypeRef references can produce undeclared GraphQL types, and unserializable TypeRef defaults can crash JSON Schema emission instead of reporting diagnostics.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Medium severity Report unserializable model defaults instead of crashing

packages/​json-schema/​src/​json-schema-emitter.ts:584

Emitting model Item {} model Registration { type: TypeRef = typeref(Item); } still crashes: modelPropertyLiteral calls #getDefaultValue (json-schema-emitter.ts:319–334), which lets the new UnserializableValueError escape instead of reporting an invalid default. Catch and report unserializable values at the emitter's default/example serialization boundaries, as OpenAPI3 already does for defaults in packages/openapi3/src/util.ts:160–172, and add an emitter regression.

Review effort: Balanced

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d925ae2-0253-4c4d-a87b-49740d9ef737
Copilot AI balanced review requested due to automatic review settings November 3, 2026 06:47
Copilot AI balanced review requested due to automatic review settings November 4, 2026 08:42

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.

🟡 Changes recommended

Uncaught TypeRef serialization errors can abort CLI compilation, while missing mutation support preserves stale references and GraphQL union traversal can incorrectly suppress schema output.

5 open issues

Review effort: Balanced

Comment thread packages/compiler/src/lib/examples.ts Resolved
Comment thread packages/compiler/src/core/types.ts Resolved
Comment thread packages/graphql/src/emitter.tsx Resolved
Copilot AI balanced review requested due to automatic review settings November 4, 2026 09:13

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.

🟡 Changes recommended

The TypeRef APIs are exposed through the stable entrypoint instead of the required experimental aliases, and mutators leave decorator type references pointing to original types.

6 open issues

Review effort: Balanced

Comment thread packages/compiler/src/index.ts Resolved
Copilot AI balanced review requested due to automatic review settings November 4, 2026 09:34

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.

Keep mixed type/reference template substitutions consistent and diagnose
unserializable OpenAPI and JSON Schema extension values without crashing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d925ae2-0253-4c4d-a87b-49740d9ef737
Copilot AI balanced review requested due to automatic review settings November 4, 2026 10:14

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.

🔵 Needs a closer look

The new public Value API and canonical TypeRef storage affect identity guarantees and recursive assignability/mutation across emitters, warranting maintainer architectural review with compiler/emitter integration validation unavailable.

2 open issues
5 resolved since last review

Review effort: Balanced

@timotheeguerin

Copy link
Copy Markdown
Member

Please keep the general lazy-owner and recursive-cloning mutator fixes in a separate PR for #7003. They affect ordinary type graphs as well as TypeRef, so they should have their own focused regression coverage and review.

The mutator changes here should remain limited to the traversal, canonical storage mapping, and template-substitution consistency required for TypeRef.

tadelesh and others added 2 commits November 4, 2026 23:20
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d925ae2-0253-4c4d-a87b-49740d9ef737
Remove generalized serialization recovery and preserve existing example, default, and extension behavior. Keep only native TypeRef integration and consolidate its feature change description.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d925ae2-0253-4c4d-a87b-49740d9ef737
Copilot AI balanced review requested due to automatic review settings November 4, 2026 15:21

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.

🔵 Needs a closer look

Compiler-maintainer review is needed because the new public value variant and cached native storage affect recursive assignability and mutation invariants that could not be exercised in the dependency-free snapshot.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems we don't do the type checking correctly here

namespace Service {}
const namespaceRef: TypeRef<Reflection.Namespace> = typeref(Service);
const modelRef: TypeRef<Reflection.Model> = namespaceRef; // Incorrectly accepted

https://cadlplayground.z22.web.core.windows.net/prs/12461/?tspconfig=ZW1pdDoKICAtICJAdHlwZXNwZWMvb3BlbmFwaTMiCmZlYXR1cmVzxyPEInJlZiI%3D&c=bmFtZXNwYWNlIFNlcnZpY2Uge30KY29uc3QgyRtSZWY6IFR5cMQJPFJlZmxlY3Rpb24uTsghPiA9IHR5cGVyZWYox0cpO8dGbW9kZWzYQk3EHcQ%2BzGw7IC8vIEluY29ycmVjdGx5IGFjY2VwdGVkCg%3D%3D&vs=%7B%7D

(though maybe it is a bug in the type relation system not just this if you can investigate)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bba3f02. The shared checker used Model for reflection types instead of their reflected kind. Added TypeRef and function regression tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9d925ae2-0253-4c4d-a87b-49740d9ef737
Copilot AI balanced review requested due to automatic review settings November 6, 2026 07:18

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.

🔵 Needs a closer look

Human assessment is needed because findings omitted from the published review identify a critical OpenAPI nullability mismatch and inconsistent TypeRef storage identity.

No open issues 🎉

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:core Issues for @typespec/compiler emitter:graphql Issues for @typespec/graphql emitter emitter:json-schema emitter:openapi3 Issues for @typespec/openapi3 emitter int:azure-specs Run integration tests against azure-rest-api-specs lib:openapi meta:website TypeSpec.io updates tspd Issues for the tspd tool

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants