fix(transformer/typescript): preserve execution order for accessor with useDefineForClassFields: false#21369
Merged
graphite-app[bot] merged 1 commit intoApr 15, 2026
Conversation
Member
Author
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Merging this PR will not alter performance
Comparing Footnotes
|
Dunqing
changed the base branch from
fix/allocator-clippy
to
graphite-base/21369
April 13, 2026 07:23
Dunqing
force-pushed
the
graphite-base/21369
branch
from
April 13, 2026 07:23
3619c61 to
345614b
Compare
Dunqing
force-pushed
the
fix/transformer-accessor-use-define-for-class-fields
branch
from
April 13, 2026 07:23
22675c4 to
44345c0
Compare
Dunqing
force-pushed
the
fix/transformer-accessor-use-define-for-class-fields
branch
from
April 13, 2026 07:47
44345c0 to
29902db
Compare
Dunqing
force-pushed
the
fix/transformer-accessor-use-define-for-class-fields
branch
3 times, most recently
from
April 14, 2026 06:40
d975300 to
e645a94
Compare
Dunqing
marked this pull request as ready for review
April 14, 2026 07:48
useDefineForClassFields: falseuseDefineForClassFields: false
useDefineForClassFields: falseuseDefineForClassFields: false
sapphi-red
approved these changes
Apr 14, 2026
Member
Author
Merge activity
|
…th `useDefineForClassFields: false` (#21369) ## Summary When `useDefineForClassFields: false` (`setPublicClassFields` assumption), the TypeScript transform moves public/TS-private field initializers into the constructor. However, private field initializers (including `accessor` backing fields from the legacy decorator transform) were left as class field declarations, causing them to execute **before** the constructor body. This broke execution order when the initializer depended on other fields that were already moved to the constructor. **Input:** ```ts class Hello { private input = { foo }; accessor util = this.input.foo(); } ``` **Before (broken):** `this.input.foo()` runs before `this.input = { foo }` ```js class Hello { constructor() { this.input = { foo }; } #_util_accessor_storage = this.input.foo(); // ← runs BEFORE constructor } ``` **After (fixed):** all instance initializers in the constructor, in source order ```js class Hello { constructor() { this.input = { foo }; this.#_util_accessor_storage = this.input.foo(); } #_util_accessor_storage; } ``` ### Approach Matches TypeScript's [`WillHoistInitializersToConstructor`](https://github.com/microsoft/TypeScript/blob/7b8cb3bdf82f400642b73173f941335775d6f730/src/compiler/transformers/classFields.ts#L339) logic in [`shouldTransformAutoAccessorsInCurrentClass`](https://github.com/microsoft/TypeScript/blob/7b8cb3bdf82f400642b73173f941335775d6f730/src/compiler/transformers/classFields.ts#L1069-L1073): - Pre-scan the class body to check if any non-private instance field has an initializer that will be hoisted - When hoisting occurs, include **all** instance fields (including `#` private) in the hoisting to preserve execution order - When no public fields are being hoisted, private fields stay as class field declarations No changes needed to the decorator/legacy transform — the fix is entirely in `transform_class_fields`. Fixes #21365 ## Test plan - [x] Added test fixture: `accessor-use-define-for-class-fields` (with hoisted + non-hoisted cases) - [x] Updated `use-define-for-class-fields-without-class-properties` expected output to match TypeScript behavior - [x] `cargo test -p oxc_transformer` — all pass - [x] `cargo run -p oxc_transform_conformance` — no regressions, previously failing test now passes 🤖 Generated with [Claude Code](https://claude.ai/code)
graphite-app
Bot
force-pushed
the
fix/transformer-accessor-use-define-for-class-fields
branch
from
April 15, 2026 01:22
e645a94 to
4fb73a7
Compare
graphite-app
Bot
deleted the
fix/transformer-accessor-use-define-for-class-fields
branch
April 15, 2026 01:26
Dunqing
added a commit
that referenced
this pull request
Apr 16, 2026
### 💥 BREAKING CHANGES - 24fb7eb allocator: [**BREAKING**] Rename `Box` and `Vec` methods (#21395) (overlookmotel) ### 🚀 Features - ce5072d parser: Support `turbopack` magic comments (#20803) (Kane Wang) - f5deb55 napi/transform: Expose `optimizeConstEnums` and `optimizeEnums` options (#21388) (Dunqing) - 24b03de data_structures: Introduce `NonNullConst` and `NonNullMut` pointer types (#21425) (overlookmotel) ### 🐛 Bug Fixes - d7a359a ecmascript: Treat update expressions as unconditionally side-effectful (#21456) (Dunqing) - 56af2f4 transformer/async-to-generator: Correct scope of inferred named FE in async-to-generator (#21458) (Dunqing) - b3ed467 minifier: Avoid illegal `var;` when folding unused arguments copy loop (#21421) (fazba) - b0e8f13 minifier: Preserve `var` inside `catch` with same-named parameter (#21366) (Dunqing) - 4fb73a7 transformer/typescript: Preserve execution order for accessor with `useDefineForClassFields: false` (#21369) (Dunqing) ### ⚡ Performance - da3cc16 parser: Refactor out `LexerContext` (#21275) (Ulrich Stark) ### 📚 Documentation - c5b19bb allocator: Reformat comments in `Arena` (#21448) (overlookmotel) - 091e88e lexer: Update doc comment about perf benefit of reading through references (#21423) (overlookmotel) - 922cbee allocator: Remove references to "bump" from comments (#21397) (overlookmotel) Co-authored-by: Dunqing <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
When
useDefineForClassFields: false(setPublicClassFieldsassumption), the TypeScript transform moves public/TS-private field initializers into the constructor. However, private field initializers (includingaccessorbacking fields from the legacy decorator transform) were left as class field declarations, causing them to execute before the constructor body. This broke execution order when the initializer depended on other fields that were already moved to the constructor.Input:
Before (broken):
this.input.foo()runs beforethis.input = { foo }After (fixed): all instance initializers in the constructor, in source order
Approach
Matches TypeScript's
WillHoistInitializersToConstructorlogic inshouldTransformAutoAccessorsInCurrentClass:#private) in the hoisting to preserve execution orderNo changes needed to the decorator/legacy transform — the fix is entirely in
transform_class_fields.Fixes #21365
Test plan
accessor-use-define-for-class-fields(with hoisted + non-hoisted cases)use-define-for-class-fields-without-class-propertiesexpected output to match TypeScript behaviorcargo test -p oxc_transformer— all passcargo run -p oxc_transform_conformance— no regressions, previously failing test now passes🤖 Generated with Claude Code