Skip to content

Fix panic on anonymous class declaration with decorator targeting ES2022#3926

Open
Copilot wants to merge 4 commits into
mainfrom
copilot/fix-anonymous-class-decorator-issue
Open

Fix panic on anonymous class declaration with decorator targeting ES2022#3926
Copilot wants to merge 4 commits into
mainfrom
copilot/fix-anonymous-class-decorator-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 16, 2026

  • Fix root cause in RuntimeSyntaxTransformer to generate names for anonymous class declarations with member decorators (matching TypeScript's ts.ts transformer)
  • Preserve assertion in esdecorator.go
  • Add compiler test case anonymousClassDecoratorEs2022.ts
  • Short-circuit ChildIsDecorated behind name == nil to avoid unnecessary traversal
  • Use tx.compilerOptions.ExperimentalDecorators.IsTrue() instead of hardcoded false
  • All checks pass: build, test, lint, format

…ng ES2022

Handle the case where an anonymous class declaration (not export default) has
decorators. Instead of asserting that a name must exist, emit the IIFE as an
expression statement.

Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/a8b3bbaf-a8f1-4207-973a-1001c1b3537d

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix tsgo panic with anonymous class declaration and member decorator Fix panic on anonymous class declaration with decorator targeting ES2022 May 16, 2026
Copilot AI requested a review from jakebailey May 16, 2026 20:13
Comment on lines -1066 to -1067
} else {
debug.Assert(classNode.Name() != nil, "A class declaration that is not a default export must have a name.")
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.

@copilot The original code said:

Debug.assertIsDefined(node.name, "A class declaration that is not a default export must have a name.");

Removing an assert that the old code does not make sense unless you can prove that the old compiler also crashes on this input

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right. I verified that TypeScript's tsc and transpileModule both handle this input without crashing — they emit let default_1 = (() => { ... })(); with a generated name. The name generation happens in TypeScript's ts.ts transformer at lines 918-923, where needsName includes HasMemberDecorators.

The Go port's RuntimeSyntaxTransformer.visitClassDeclaration was only generating names for namespace exports (exported && name == nil) but was missing the member decorator case. I've reverted the esdecorator.go change (assertion preserved) and instead fixed the root cause in runtimesyntax.go to also generate a name when ChildIsDecorated is true, matching the TypeScript behavior. See def1f4a.

…ators

Instead of removing the assertion in esdecorator.go, fix the root cause in
RuntimeSyntaxTransformer. When a class declaration has member decorators but
no name, generate a name (matching TypeScript's ts.ts transformer behavior
at lines 918-923). This ensures the esDecorators transformer always receives
a named class declaration, preserving the assertion.

Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/cf0722bd-efb1-48e2-bd50-82f8c7bb4fb6

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI requested a review from jakebailey May 16, 2026 21:02
@jakebailey jakebailey marked this pull request as ready for review May 16, 2026 21:08
Copilot AI review requested due to automatic review settings May 16, 2026 21:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a panic in the ES decorator transformer when an anonymous class declaration contains member decorators (e.g. class { @x m() {} } with target: es2022). The runtime-syntax transformer previously only generated a name for anonymous classes that were namespace exports; it now also generates a name when the class has decorated members, matching the upstream ts.ts transformer behavior so the downstream ES decorator transformer's "name is defined" invariant is upheld.

Changes:

  • Extend RuntimeSyntaxTransformer.visitClassDeclaration to also generate a name when ast.ChildIsDecorated reports decorated members.
  • Add a compiler test case anonymousClassDecoratorEs2022.ts with corresponding baselines (.js, .types, .symbols, .errors.txt).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/transformers/tstransforms/runtimesyntax.go Adds ChildIsDecorated to the name-generation condition for anonymous class declarations.
testdata/tests/cases/compiler/anonymousClassDecoratorEs2022.ts New minimal repro test for an anonymous decorated class at target: es2022.
testdata/baselines/reference/compiler/anonymousClassDecoratorEs2022.js JS emit baseline showing the generated default_1 name and __esDecorate lowering.
testdata/baselines/reference/compiler/anonymousClassDecoratorEs2022.types Types baseline for the new test.
testdata/baselines/reference/compiler/anonymousClassDecoratorEs2022.symbols Symbols baseline for the new test.
testdata/baselines/reference/compiler/anonymousClassDecoratorEs2022.errors.txt Expected diagnostics baseline (TS1211 and TS2304).

name := tx.Visitor().VisitNode(node.Name())
if exported && name == nil {
needsName := (exported && name == nil) ||
ast.ChildIsDecorated(false /*useLegacyDecorators*/, node.AsNode(), nil)
Comment on lines +679 to +681
needsName := (exported && name == nil) ||
ast.ChildIsDecorated(false /*useLegacyDecorators*/, node.AsNode(), nil)
if needsName && name == nil {
@jakebailey
Copy link
Copy Markdown
Member

@copilot apply changes based on the comments in this thread

…orators option

- Short-circuit ChildIsDecorated call behind name==nil check to avoid
  unnecessary member traversal when the class already has a name
- Use tx.compilerOptions.ExperimentalDecorators.IsTrue() instead of
  hardcoded false, consistent with metadata.go and classfields.go

Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/f3be6820-9af2-4fd8-89f4-2d811a054426

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 16, 2026

@copilot apply changes based on the comments in this thread

Applied both changes in aff8b1a:

  1. Short-circuited ChildIsDecorated behind name == nil to avoid unnecessary member traversal when the class already has a name
  2. Replaced hardcoded false with tx.compilerOptions.ExperimentalDecorators.IsTrue(), consistent with metadata.go and classfields.go

Copilot AI requested a review from jakebailey May 16, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tsgo panics when an anonymous class declaration has a member decorator when downleveling to ES2022

3 participants