Skip to content

Implement erasable enum annotations - #65421

Draft
magic-akari wants to merge 4 commits into
microsoft:mainfrom
magic-akari:feat/erasable-enum
Draft

magic-akari wants to merge 4 commits into
microsoft:mainfrom
magic-akari:feat/erasable-enum

Conversation

@magic-akari

@magic-akari magic-akari commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

The goal is to provide stronger type constraints than an ordinary object without requiring an enum-specific runtime transform. As described in #60790, the as const object pattern requires a verbose typeof X[keyof typeof X] alias and produces a plain union rather than a nominal enum type.

This PR adds erasable : enum annotations, combining ordinary JavaScript object literals with enum types and named member types:

export const Mode: enum = {
    Read: "read",
    Write: "write",
};

let mode: Mode = Mode.Read;
let read: Mode.Read = Mode.Read;

The implementation separates parsing, type checking, and emission:

  • Parsing and AST: Reuse the existing type-reference and object-literal syntax. No parser implementation changes or new object AST nodes are required. The binder and checker recognize the : enum annotation.
  • Type checking: Give the declaration an associated nominal enum type and readonly member types. Member shape and value restrictions are enforced by the checker to keep TypeScript’s type model consistent.
  • JavaScript emission: Erase the annotation while preserving ordinary object initialization, expressions, and member accesses. Downstream transpilers and type strippers do not need to reproduce TypeScript’s semantic checks or perform an enum-specific runtime transformation. The syntax satisfies the erasableSyntaxOnly constraint by construction.
  • Declaration emission: Retain : enum and serialize evaluated member values into .d.ts files. For example, A: 1 << 2 becomes A: 4 only in declaration output; JavaScript retains A: 1 << 2. Under isolatedDeclarations, self-contained constant expressions are supported, while references to symbols outside the enum are rejected, including constants declared in the same file.

For named variable statements annotated with : enum, the checker applies the following rules. These are TypeScript semantic restrictions, not additional validation requirements for type-stripping tools.

Form Example Accepted
Explicit properties with identifier or string literal keys A: 1, "name": "value", "1": "one" Yes
Numeric literal keys 1: "one" No
Computed keys, including constant keys ["A"]: 1, [key]: 1 No
Constant expressions A: 1 << 2, B: "a" + "b" Yes
Compile-time constant references A: n, where const n = 1 Yes; TS9020 under isolatedDeclarations
Parenthesized objects and values ({ A: ((1)) }) Yes; parentheses are transparent
Spread properties ...other No
Shorthand properties A No; use A: A
Methods A() {} No
Getters and setters get A() { return 1; }, set A(v) {} No
Other value types A: true, A: null, A: 1n, A: Symbol(), A: {} No
Runtime values, mutable bindings, or broad number/string references A: Math.random(), A: n where let n = 1 No
Assertions, satisfies, and non-null assertions A: 1 as number, A: 1 satisfies number, A: value! No
Null prototype configuration __proto__: null, "__proto__": (null) Yes; this does not declare a member
Self-reference during initialization const E: enum = { A: 1, B: E.A } No; ordinary object initialization rules apply

@typescript-automation typescript-automation Bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Nov 10, 2026
@magic-akari magic-akari changed the title Implement erasable enum annotations in the Go compiler Implement erasable enum annotations Nov 10, 2026
@magic-akari
magic-akari marked this pull request as ready for review November 10, 2026 04:19
Copilot AI balanced review requested due to automatic review settings November 10, 2026 04:19
@typescript-automation

Copy link
Copy Markdown
Contributor

The TypeScript team hasn't accepted the linked issue #60790. If you can get it accepted, this PR will have a better chance of being reviewed.

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

Enum literals still fail AST traversal because isExpression rejects them and incorrectly trigger an isolatedModules diagnostic for imported string initializers.

2 open issues
7 files not reviewed
  • tsc/internal/api/encoder/decoder_generated.go: Generated file
  • tsc/internal/api/encoder/encoder_generated.go: Generated file
  • tsc/internal/api/enum_values_generated.go: Generated file
  • tsc/internal/ast/ast_generated.go: Generated file
  • tsc/internal/ast/kind_generated.go: Generated file
  • tsc/internal/ast/kind_stringer_generated.go: Generated file
  • tsc/internal/diagnostics/diagnostics_generated.go: Generated file
What changed in this PR

Adds erasable : enum annotations to the native TypeScript compiler, giving object literals nominal enum types without changing their JavaScript evaluation. This implements the annotation approach discussed in #60790 and builds on #61414.

Changes:

  • Adds parsing, binding, type checking, and diagnostics for enum literals.
  • Emits ordinary JavaScript objects while preserving enum annotations and member types in declarations.
  • Extends AST APIs and adds regression tests and baselines.
File Description
tsc/​testdata/​tests/​cases/​compiler/​enumLiteralSimple.ts Tests basic enum annotations.
tsc/​testdata/​tests/​cases/​compiler/​enumLiteralModules.ts Tests exports, imports, and aliases.
tsc/​testdata/​tests/​cases/​compiler/​enumLiteralErrors.ts Tests invalid members and assignments.
tsc/​testdata/​tests/​cases/​compiler/​enumLiteralErasable.ts Tests erasure, narrowing, and scopes.
tsc/​testdata/​tests/​cases/​compiler/​enumLiteralDeclarations.ts Tests declaration visibility and aliases.
tsc/​testdata/​tests/​cases/​compiler/​enumLiteralBasics.ts Tests member types and assignability.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralSimple.types Records basic inferred types.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralSimple.symbols Records basic symbol bindings.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralSimple.js Records basic JavaScript emit.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralModules(module=esnext).types Records ES module types.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralModules(module=esnext).symbols Records ES module bindings.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralModules(module=esnext).js Records ES module and declaration emit.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralModules(module=commonjs).types Records CommonJS types.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralModules(module=commonjs).symbols Records CommonJS bindings.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralModules(module=commonjs).js Records CommonJS and declaration emit.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErrors.types Records types for invalid cases.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErrors.symbols Records bindings for invalid cases.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErrors.js Records emit for invalid cases.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErrors.errors.txt Records expected diagnostics.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErasable(target=esnext).types Records ESNext narrowing and types.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErasable(target=esnext).symbols Records ESNext symbol bindings.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErasable(target=esnext).js Records ESNext erasure and declarations.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErasable(target=es2015).types Records ES2015 narrowing and types.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErasable(target=es2015).symbols Records ES2015 symbol bindings.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralErasable(target=es2015).js Records ES2015 erasure and declarations.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralDeclarations(module=esnext).types Records exported and namespace types.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralDeclarations(module=esnext).symbols Records declaration-related bindings.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralDeclarations(module=esnext).js Records ES module declaration visibility.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralDeclarations(module=commonjs).types Records CommonJS exported types.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralDeclarations(module=commonjs).symbols Records CommonJS declaration bindings.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralDeclarations(module=commonjs).js Records CommonJS declaration visibility.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralBasics.types Records member and assignment types.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralBasics.symbols Records member symbol resolution.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralBasics.js Records basic erasure output.
tsc/​testdata/​baselines/​reference/​compiler/​enumLiteralBasics.errors.txt Records assignment and initializer errors.
tsc/​internal/​transpile/​enum_literal_test.go Tests transpilation and declaration round trips.
tsc/​internal/​transformers/​tstransforms/​typeeraser.go Converts enum literals to ordinary objects.
tsc/​internal/​transformers/​declarations/​transform.go Preserves enum members in declarations.
tsc/​internal/​printer/​printer.go Prints enum literal member syntax.
tsc/​internal/​printer/​printer_test.go Tests enum literal printing.
tsc/​internal/​parser/​parser.go Parses contextual annotations and members.
tsc/​internal/​parser/​enum_literal_test.go Tests parsing, recovery, and contexts.
tsc/​internal/​diagnostics/​diagnostics_generated.go Adds the generated prototype-key diagnostic.
tsc/​internal/​diagnostics/​diagnosticMessages.json Defines the prototype-key diagnostic.
tsc/​internal/​diagnostics/​diagnosticMessages.generated.json Adds generated diagnostic text.
tsc/​internal/​checker/​grammarchecks.go Allows enum literal ambient initializers.
tsc/​internal/​checker/​erasable_enum_test.go Tests semantics and declaration conflicts.
tsc/​internal/​checker/​emitsupport.go Tracks enum literal declaration visibility.
tsc/​internal/​checker/​checker.go Adds enum literal typing and validation.
tsc/​internal/​binder/​referenceresolver.go Handles exported enum literal references.
tsc/​internal/​binder/​enum_literal_test.go Tests symbols and prohibited merging.
tsc/​internal/​binder/​binder.go Binds variables and literals as enum symbols.
tsc/​internal/​ast/​utilities.go Adds enum literal recognition helpers.
tsc/​internal/​ast/​precedence.go Classifies enum literal precedence.
tsc/​internal/​ast/​kind_stringer_generated.go Updates generated syntax-kind names.
tsc/​internal/​ast/​kind_generated.go Adds the enum literal syntax kind.
tsc/​internal/​ast/​deepclone_test.go Tests enum literal cloning.
tsc/​internal/​ast/​ast.go Adds member access and subtree facts.
tsc/​internal/​ast/​ast_generated.go Adds generated Go AST support.
tsc/​internal/​api/​enum_values_generated.go Exposes the new syntax-kind value.
tsc/​internal/​api/​encoder/​encoder_generated.go Encodes enum literal children.
tsc/​internal/​api/​encoder/​decoder_generated.go Decodes enum literal nodes.
tools/​scripts/​tsc/​ast.json Defines the enum literal AST schema.
packages/​typescript/​src/​ast/​visitor.generated.ts Adds enum literal child traversal.
packages/​typescript/​src/​ast/​is.generated.ts Adds the enum literal type guard.
packages/​typescript/​src/​ast/​factory.generated.ts Adds factories, cloning, and traversal.
packages/​typescript/​src/​ast/​ast.generated.ts Defines the public enum literal interface.
packages/​typescript/​src/​api/​node/​protocol.generated.ts Describes enum literal protocol children.
packages/​typescript/​enums/​syntaxKind.js Exposes the runtime syntax kind.
packages/​typescript/​enums/​syntaxKind.d.ts Declares the public syntax kind.

Review effort: Balanced

Comment thread tsc/internal/checker/checker.go Resolved
@magic-akari
magic-akari marked this pull request as draft November 10, 2026 04:38
Give object literals nominal enum types with erasable `: enum` annotations.
Preserve runtime behavior and member types in declaration files.
@magic-akari
magic-akari marked this pull request as ready for review November 10, 2026 08:47
@RyanCavanaugh
Ryan Cavanaugh (RyanCavanaugh) marked this pull request as draft November 10, 2026 17:53

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

For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

as enum assertion for object literals

2 participants