You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Incorrect class #private fields initialization order with target ES2022 and useDefineForClassFields set to false Β #54206
With 5.0.4 and useDefineForClassFields: false, target: ES2022 the above code fails in runtime because #bar = this.buz.wow; stays initialized before constructor, this doesn't happen if "normal private" is used (private bar = this.buz.wow;). Such construct works without issues on 4.9.5.
π Expected behavior
The initialization is done in the same way as on 4.9.5.
I don't think this is supposed to work. The fact that private fields were transpiled to an assignment inside the constructor seems like a bug, because useDefineForClassFields is only supposed to affect public properties (which can be shadowed by the base class and therefore suffers from the define vs. set issue). Private fields should always be initialized before the parameter properties, solely by how scoping works.
I don't think this is supposed to work. The fact that private fields were transpiled to an assignment inside the constructor seems like a bug, because useDefineForClassFields is only supposed to affect public properties (which can be shadowed by the base class and therefore suffers from the define vs. set issue). Private fields should always be initialized before the parameter properties, solely by how scoping works.
That's fine as long as there is an error produced on compilation time. Having this quietly passing produces a lot of hard to understand issues on run-time.
I don't think this is supposed to work. The fact that private fields were transpiled to an assignment inside the constructor seems like a bug, because useDefineForClassFields is only supposed to affect public properties (which can be shadowed by the base class and therefore suffers from the define vs. set issue). Private fields should always be initialized before the parameter properties, solely by how scoping works.
Joshua Chen (@Josh-Cena) if this is the expected behavior, I think that, at least, should be a compilation error....
classBuzz{wow='one'}classFoo{
#bar =this.buz.wow;^^^_This is propertyisuninitialized... orsomethingsimilarconstructor(privatebuz: Buzz){}}newFoo(newBuzz())
I don't think this is supposed to work. The fact that private fields were transpiled to an assignment inside the constructor seems like a bug, because useDefineForClassFields is only supposed to affect public properties (which can be shadowed by the base class and therefore suffers from the define vs. set issue). Private fields should always be initialized before the parameter properties, solely by how scoping works.
I think this is supposed to work, but we didn't handle this case correctly when private fields were introduced. The ideal solution would be for parameter properties to work with both private fields and --useDefineForClassFields. For such a feature to work correctly, we would have to choose between two different behaviors:
Parameter properties are initialized before all class fields, or
Parameter properties are initialized after all class fields
Prior to the adoption of private names, the TypeScript emit followed (1), which I believe is still the best approach.
If we want to correctly preserve these semantics and this evaluation order in the presence of private names or --useDefineForClassFields, then the correct emit would be to:
declare all of the class fields, including private-named fields and those introduced by parameter properties
Initialize the fields introduced by parameter properties
Initialize the remaining fields in the order they appear in the class body.
This means moving the field initializers into the constructor body:
If we want to correctly preserve these semantics and this evaluation order in the presence of private names or --useDefineForClassFields, then the correct emit would be to:
declare all of the class fields, including private-named fields and those introduced by parameter properties
Initialize the fields introduced by parameter properties
Initialize the remaining fields in the order they appear in the class body.
This means moving the field initializers into the constructor body:
the later requires separating declaration from initialization; this is almost like requiring to write let value; value = 42 instead of a simple const value = 42
I don't think constructor should do any complicated work, and I'm too used to it being completely empty
β¦ and I assumed that this works for #fields the same way. Provided that TypeScript does #54206 (comment), it shouldn't be any less intuitive.
TypeError: Cannot read properties of undefined (reading 'create')
This appears to be the native-private-field gap adjacent to #50971 / #55028:
with useDefineForClassFields: false, an ordinary field initializer is moved
into the constructor after the parameter-property assignment, but a #private
initializer remains native and the compiler also does not issue TS2729.
We encountered this in emitted NestJS resolver code. Type-checking and
source-level tests passed; a post-build constructor check reproduced the runtime
failure before rollout. The local workaround is to use TypeScript private or
initialize the #private field explicitly inside the constructor.
Activity
Michsior14 commentedon Jun 2, 2023
Still an issue in 5.1.3.
Josh-Cena commentedon Jun 2, 2023
I don't think this is supposed to work. The fact that private fields were transpiled to an assignment inside the constructor seems like a bug, because
useDefineForClassFieldsis only supposed to affect public properties (which can be shadowed by the base class and therefore suffers from the define vs. set issue). Private fields should always be initialized before the parameter properties, solely by how scoping works.Michsior14 commentedon Jun 3, 2023
That's fine as long as there is an error produced on compilation time. Having this quietly passing produces a lot of hard to understand issues on run-time.
Michsior14 commentedon Jun 29, 2023
Any updates on compilation errors / order fix?
Michsior14 commentedon Jul 12, 2023
Same on 5.1.6.
tonivj5 commentedon Jul 26, 2023
Joshua Chen (@Josh-Cena) if this is the expected behavior, I think that, at least, should be a compilation error....
rbuckton commentedon Jul 26, 2023
I think this is supposed to work, but we didn't handle this case correctly when private fields were introduced. The ideal solution would be for parameter properties to work with both private fields and
--useDefineForClassFields. For such a feature to work correctly, we would have to choose between two different behaviors:Prior to the adoption of private names, the TypeScript emit followed (1), which I believe is still the best approach.
If we want to correctly preserve these semantics and this evaluation order in the presence of private names or
--useDefineForClassFields, then the correct emit would be to:This means moving the field initializers into the constructor body:
tonivj5 commentedon Jul 26, 2023
That's the behavior I expected. Cool to know it's an unexpected breaking change, and this issue is someting fixable π
Michsior14 commentedon Dec 1, 2023
5.3.2 still affected
Michsior14 commentedon Dec 11, 2023
I forgot to mention a small workaround I wrote couple months ago. It's possible to detect wrongly defined properties using
eslintand this rule:Hope this helps someone to prevent runtime issues.
Michsior14 commentedon Mar 25, 2024
No change in 5.4.3
Michsior14 commentedon Jun 21, 2024
5.5.2 also affected
parzhitsky commentedon Feb 8, 2025
I came here because I'd like to write code like this:
β¦ instead of like this:
β¦ because:
let value; value = 42instead of a simpleconst value = 42β¦ and I assumed that this works for
#fieldsthe same way. Provided that TypeScript does #54206 (comment), it shouldn't be any less intuitive.dilame commentedon Jul 13, 2026
Confirmed this still reproduces on both current stable and nightly:
7.0.27.1.0-dev.20260713.1Compiler options:
{ "target": "ESNext", "module": "NodeNext", "moduleResolution": "NodeNext", "useDefineForClassFields": false, "strict": true }Minimal reproduction:
For both versions,
tscexits successfully with no diagnostic. The emittedfield remains ahead of the parameter-property assignment:
Running the output exits 1 with:
This appears to be the native-private-field gap adjacent to #50971 / #55028:
with
useDefineForClassFields: false, an ordinary field initializer is movedinto the constructor after the parameter-property assignment, but a
#privateinitializer remains native and the compiler also does not issue TS2729.
We encountered this in emitted NestJS resolver code. Type-checking and
source-level tests passed; a post-build constructor check reproduced the runtime
failure before rollout. The local workaround is to use TypeScript
privateorinitialize the
#privatefield explicitly inside the constructor.