Skip to content

fix(transformer/class): parent generated constructors to class scope#23222

Merged
graphite-app[bot] merged 1 commit into
mainfrom
codex/fix-transformer-class-scope
Jun 10, 2026
Merged

fix(transformer/class): parent generated constructors to class scope#23222
graphite-app[bot] merged 1 commit into
mainfrom
codex/fix-transformer-class-scope

Conversation

@camc314

@camc314 camc314 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Parent synthesized constructors for TypeScript class field lowering under the transformed class body scope instead of the current traversal scope.
  • Update the transform conformance snapshot now that the generated constructor scope matches the rebuilt semantic scope.

Details

When useDefineForClassFields lowering needs to synthesize a constructor, the traversal is still positioned in the scope that encloses the class. Static blocks created in the same transform already use class_scope_id, but generated constructors used create_child_scope_of_current, so their semantic parent could point outside the class.

This changes the generated constructor scope creation to use create_child_scope(class_scope_id, ...), matching where the constructor actually lives in the class body.

Example

class Cls {
  y = 1;
  [key] = 2;
}

Conceptually this transform creates a constructor for the hoisted field initializers:

class Cls {
  constructor() {
    this.y = 1;
    this[key] = 2;
  }
}

Before this change, the constructor AST was nested in Cls, but its semantic scope was parented to the enclosing scope:

flowchart TD
  Program["Program scope"] --> Class["Class body scope: Cls"]
  Program --> GeneratedCtor["Generated constructor scope"]
  Class --> StaticBlocks["Generated static blocks"]
Loading

After this change, the generated constructor scope is parented to the class body scope, matching the rebuilt semantic tree:

flowchart TD
  Program["Program scope"] --> Class["Class body scope: Cls"]
  Class --> GeneratedCtor["Generated constructor scope"]
  Class --> StaticBlocks["Generated static blocks"]
Loading

The same mismatch showed up in the accessor backing field fixture:

class Hello {
  private input = { foo };
  accessor util = this.input.foo();
}

AI assistance: OpenAI Codex was used to prepare this change.

@github-actions github-actions Bot added the A-transformer Area - Transformer / Transpiler label Jun 10, 2026
@codspeed-hq

codspeed-hq Bot commented Jun 10, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 52 untouched benchmarks
⏩ 19 skipped benchmarks1


Comparing codex/fix-transformer-class-scope (f06ce9f) with main (23e0474)

Open in CodSpeed

Footnotes

  1. 19 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@camc314 camc314 marked this pull request as ready for review June 10, 2026 09:53
Copilot AI review requested due to automatic review settings June 10, 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.

Pull request overview

Fixes a semantic-scope parenting bug in the TypeScript class-fields lowering path: when a synthesized constructor is generated (for useDefineForClassFields-style lowering), its scope is now parented to the class body scope instead of the enclosing traversal scope, aligning the “after transform” semantic tree with the rebuilt semantic tree.

Changes:

  • Create the synthesized constructor’s scope via ctx.create_child_scope(class_scope_id, ...) instead of create_child_scope_of_current(...).
  • Update transform conformance snapshots to reflect the corrected scope parenting (and the resulting pass-count change).

Reviewed changes

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

File Description
crates/oxc_transformer/src/typescript/class.rs Parents generated constructor scopes to class_scope_id to match the constructor’s actual placement within the class body.
tasks/transform_conformance/snapshots/oxc.snap.md Updates snapshot output to remove prior scope-parent mismatch reports and reflect the new pass count.

@camc314 camc314 self-assigned this Jun 10, 2026
@camc314 camc314 added the 0-merge Merge with Graphite Merge Queue label Jun 10, 2026

camc314 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

…23222)

## Summary
- Parent synthesized constructors for TypeScript class field lowering under the transformed class body scope instead of the current traversal scope.
- Update the transform conformance snapshot now that the generated constructor scope matches the rebuilt semantic scope.

## Details
When `useDefineForClassFields` lowering needs to synthesize a constructor, the traversal is still positioned in the scope that encloses the class. Static blocks created in the same transform already use `class_scope_id`, but generated constructors used `create_child_scope_of_current`, so their semantic parent could point outside the class.

This changes the generated constructor scope creation to use `create_child_scope(class_scope_id, ...)`, matching where the constructor actually lives in the class body.

## Example
```ts
class Cls {
  y = 1;
  [key] = 2;
}
```

Conceptually this transform creates a constructor for the hoisted field initializers:

```ts
class Cls {
  constructor() {
    this.y = 1;
    this[key] = 2;
  }
}
```

Before this change, the constructor AST was nested in `Cls`, but its semantic scope was parented to the enclosing scope:

```mermaid
flowchart TD
  Program["Program scope"] --> Class["Class body scope: Cls"]
  Program --> GeneratedCtor["Generated constructor scope"]
  Class --> StaticBlocks["Generated static blocks"]
```

After this change, the generated constructor scope is parented to the class body scope, matching the rebuilt semantic tree:

```mermaid
flowchart TD
  Program["Program scope"] --> Class["Class body scope: Cls"]
  Class --> GeneratedCtor["Generated constructor scope"]
  Class --> StaticBlocks["Generated static blocks"]
```

The same mismatch showed up in the accessor backing field fixture:

```ts
class Hello {
  private input = { foo };
  accessor util = this.input.foo();
}
```

AI assistance: OpenAI Codex was used to prepare this change.
@graphite-app graphite-app Bot force-pushed the codex/fix-transformer-class-scope branch from f06ce9f to 2c28748 Compare June 10, 2026 10:01
@graphite-app graphite-app Bot merged commit 2c28748 into main Jun 10, 2026
29 checks passed
@graphite-app graphite-app Bot removed the 0-merge Merge with Graphite Merge Queue label Jun 10, 2026
@graphite-app graphite-app Bot deleted the codex/fix-transformer-class-scope branch June 10, 2026 10:06
Boshen added a commit that referenced this pull request Jun 15, 2026
### 💥 BREAKING CHANGES

- 7a24911 codegen: [**BREAKING**] Borrow sourcemaps from codegen
(#23422) (Boshen)
- bb0ed44 transformer: [**BREAKING**] Disable styled-components
transpileTemplateLiterals by default (#23171) (Boshen)

### 🚀 Features

- 1490a0a linter/react: Implement react-compiler rule (#23202) (Boshen)
- 6c0bdf0 transformer/react-refresh: Support `module.property.useHook()`
(#23190) (Dunqing)
- 47991bd semantic: Report TS1228 for invalid type predicates (#23174)
(camc314)
- 1d3af58 parser: Add TS2398 parameter property diagnostic (#23216)
(camc314)
- 44313da semantic: Add `scope_is_descendant_of` api (#22313) (camc314)
- e5050c0 parser: Improve diagnostic for rest initializer (#23205)
(camc314)
- ec266bb transformer: Run React Compiler as a feature-gated transform
pass (#23201) (Boshen)
- e7374fe parser: Report error for `const` modifier on interface type
parameter (#23173) (camc314)
- a7c1c9b parser: Report ambient definite variable assertions (#23165)
(camc314)
- d169fcd parser: Report invalid class definite assertions (#23164)
(camc314)
- 00244d8 parser: Report definite property initializer errors (#23160)
(camc314)

### 🐛 Bug Fixes

- 52d0c31 transformer: Replace ambient dot defines (#23231) (camc314)
- 2c28748 transformer/class: Parent generated constructors to class
scope (#23222) (camc314)
- 8edd234 parser: Report accessor definite assertion on token (#23203)
(camc314)
- de38a3f react_compiler: Keep imports referenced only by a local
re-export (#23176) (Boshen)
- f5721c2 codegen: Preserve parentheses around `intrinsic` type
reference (#23156) (Boshen)
- e89f81d parser: Don't emit TS1477 for parenthesized instantiation
expression (#23147) (Boshen)
- 8a04149 parser: Reject module-referencing imports/exports in a
namespace body (#22829) (Boshen)

### ⚡ Performance

- 2783295 parser: Table-driven operator precedence lookup (#23346)
(Boshen)
- 231d5de parser: Single-match member expression dispatch (#23347)
(Boshen)
- e89729b codegen: Accept one-shot wrap closures (#23265) (camc314)
- a6c11fa parser: Force-inline read_non_decimal to fold per-digit number
dispatch (#23157) (Boshen)
- d74964c parser: Store class definite assertion offset (#23170)
(camc314)
- f0fda4d parser: Shrink-wrap cold diagnostic tails out of hot frames
(#23159) (Boshen)
- a082180 parser: Store definite assertion offset (#23167) (camc314)
- 534f9c6 oxc: Conditionally rebuild semantic in compiler pipeline
(#23153) (Boshen)
- b435c6a parser: Skip checkpoint for `infer T extends U` constraint in
disallow context (#23128) (Boshen)
- 7464dce parser: Peek instead of checkpoint/rewind for `export default`
modifier (#23124) (Boshen)
- 80a9a32 parser: Fast-path single-keyword TS declarations (#23083)
(Boshen)
- da1a6c6 diagnostics: Migrate to allocation-optimized oxc-miette
(#23094) (Boshen)
- b7b08ce parser: Peek once for the static modifier disambiguation
(#23079) (Boshen)
- e7e07a3 parser: Fold unary dispatch into a single match (#23076)
(Boshen)

### 📚 Documentation

- d241add semantic: Add `AGENTS.md` test guidance for agents (#23441)
(camc314)
- 026f1ae parser: Add `AGENTS.md` test guidance for agents (#23440)
(camc314)
- 09755ac transformer: Add `AGENTS.md` test guidance for agents (#23439)
(camc314)
- e6bdfd4 lexer: Correct reference link for `byte_handlers!` (#23313)
(Dunqing)
- 65b6d7a allocator: Fix memory leaks in `Arena` examples (#23257)
(overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
camc314 added a commit that referenced this pull request Jul 3, 2026
…23222)

## Summary
- Parent synthesized constructors for TypeScript class field lowering under the transformed class body scope instead of the current traversal scope.
- Update the transform conformance snapshot now that the generated constructor scope matches the rebuilt semantic scope.

## Details
When `useDefineForClassFields` lowering needs to synthesize a constructor, the traversal is still positioned in the scope that encloses the class. Static blocks created in the same transform already use `class_scope_id`, but generated constructors used `create_child_scope_of_current`, so their semantic parent could point outside the class.

This changes the generated constructor scope creation to use `create_child_scope(class_scope_id, ...)`, matching where the constructor actually lives in the class body.

## Example
```ts
class Cls {
  y = 1;
  [key] = 2;
}
```

Conceptually this transform creates a constructor for the hoisted field initializers:

```ts
class Cls {
  constructor() {
    this.y = 1;
    this[key] = 2;
  }
}
```

Before this change, the constructor AST was nested in `Cls`, but its semantic scope was parented to the enclosing scope:

```mermaid
flowchart TD
  Program["Program scope"] --> Class["Class body scope: Cls"]
  Program --> GeneratedCtor["Generated constructor scope"]
  Class --> StaticBlocks["Generated static blocks"]
```

After this change, the generated constructor scope is parented to the class body scope, matching the rebuilt semantic tree:

```mermaid
flowchart TD
  Program["Program scope"] --> Class["Class body scope: Cls"]
  Class --> GeneratedCtor["Generated constructor scope"]
  Class --> StaticBlocks["Generated static blocks"]
```

The same mismatch showed up in the accessor backing field fixture:

```ts
class Hello {
  private input = { foo };
  accessor util = this.input.foo();
}
```

AI assistance: OpenAI Codex was used to prepare this change.
camc314 pushed a commit that referenced this pull request Jul 3, 2026
### 💥 BREAKING CHANGES

- 7a24911 codegen: [**BREAKING**] Borrow sourcemaps from codegen
(#23422) (Boshen)
- bb0ed44 transformer: [**BREAKING**] Disable styled-components
transpileTemplateLiterals by default (#23171) (Boshen)

### 🚀 Features

- 1490a0a linter/react: Implement react-compiler rule (#23202) (Boshen)
- 6c0bdf0 transformer/react-refresh: Support `module.property.useHook()`
(#23190) (Dunqing)
- 47991bd semantic: Report TS1228 for invalid type predicates (#23174)
(camc314)
- 1d3af58 parser: Add TS2398 parameter property diagnostic (#23216)
(camc314)
- 44313da semantic: Add `scope_is_descendant_of` api (#22313) (camc314)
- e5050c0 parser: Improve diagnostic for rest initializer (#23205)
(camc314)
- ec266bb transformer: Run React Compiler as a feature-gated transform
pass (#23201) (Boshen)
- e7374fe parser: Report error for `const` modifier on interface type
parameter (#23173) (camc314)
- a7c1c9b parser: Report ambient definite variable assertions (#23165)
(camc314)
- d169fcd parser: Report invalid class definite assertions (#23164)
(camc314)
- 00244d8 parser: Report definite property initializer errors (#23160)
(camc314)

### 🐛 Bug Fixes

- 52d0c31 transformer: Replace ambient dot defines (#23231) (camc314)
- 2c28748 transformer/class: Parent generated constructors to class
scope (#23222) (camc314)
- 8edd234 parser: Report accessor definite assertion on token (#23203)
(camc314)
- de38a3f react_compiler: Keep imports referenced only by a local
re-export (#23176) (Boshen)
- f5721c2 codegen: Preserve parentheses around `intrinsic` type
reference (#23156) (Boshen)
- e89f81d parser: Don't emit TS1477 for parenthesized instantiation
expression (#23147) (Boshen)
- 8a04149 parser: Reject module-referencing imports/exports in a
namespace body (#22829) (Boshen)

### ⚡ Performance

- 2783295 parser: Table-driven operator precedence lookup (#23346)
(Boshen)
- 231d5de parser: Single-match member expression dispatch (#23347)
(Boshen)
- e89729b codegen: Accept one-shot wrap closures (#23265) (camc314)
- a6c11fa parser: Force-inline read_non_decimal to fold per-digit number
dispatch (#23157) (Boshen)
- d74964c parser: Store class definite assertion offset (#23170)
(camc314)
- f0fda4d parser: Shrink-wrap cold diagnostic tails out of hot frames
(#23159) (Boshen)
- a082180 parser: Store definite assertion offset (#23167) (camc314)
- 534f9c6 oxc: Conditionally rebuild semantic in compiler pipeline
(#23153) (Boshen)
- b435c6a parser: Skip checkpoint for `infer T extends U` constraint in
disallow context (#23128) (Boshen)
- 7464dce parser: Peek instead of checkpoint/rewind for `export default`
modifier (#23124) (Boshen)
- 80a9a32 parser: Fast-path single-keyword TS declarations (#23083)
(Boshen)
- da1a6c6 diagnostics: Migrate to allocation-optimized oxc-miette
(#23094) (Boshen)
- b7b08ce parser: Peek once for the static modifier disambiguation
(#23079) (Boshen)
- e7e07a3 parser: Fold unary dispatch into a single match (#23076)
(Boshen)

### 📚 Documentation

- d241add semantic: Add `AGENTS.md` test guidance for agents (#23441)
(camc314)
- 026f1ae parser: Add `AGENTS.md` test guidance for agents (#23440)
(camc314)
- 09755ac transformer: Add `AGENTS.md` test guidance for agents (#23439)
(camc314)
- e6bdfd4 lexer: Correct reference link for `byte_handlers!` (#23313)
(Dunqing)
- 65b6d7a allocator: Fix memory leaks in `Arena` examples (#23257)
(overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-transformer Area - Transformer / Transpiler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants