- Notifications
You must be signed in to change notification settings - Fork13.2k
Description
🔎 Search Terms
__setFunctionName
class static name
ES decorators static name
__esDecorate static name
🕗 Version & Regression Information
5.0.4-5.9.3
⏯ Playground Link
💻 Code
constnoop=(Self:any,ctx:ClassDecoratorContext)=>{};constrand=()=>4;@noopexportdefaultclass{staticgetname(){return2434;}}(@noopclass{staticgetname(){return2434;}});(@noopclass__A{staticgetname(){return2434;}});@noopclass__B{staticgetname(){return2434;}}var__C= @noopclass{staticgetname(){return2434;}};var__D={[rand()]: @noopclass{staticgetname(){return2434;}}};// Unconditionally throws at runtime:@noopclass__E{static #a=2434;staticgetname(){returnthis.#a;}}// Undecorated classes are broken as well:var__F=class{staticgetname(){return2434;}// target≤es2021 => '__F'// target≥es2022 => 2434static{}}
🙁 Actual behavior
Staticname is overridden by__setFunctionName().
🙂 Expected behavior
Staticname should work as defined in the class code.
Additional information about the issue
Generated code for the ES decorators breaks custom staticname property on the decorated class. The only syntax that works is plainstatic name; field declaration; other kinds (methods,get/set/accessor) are broken.
The issue reproduces iff the compiler decides to inject thestatic { __setFunctionName(this, ...) } block, whichunconditionally overrides the staticname descriptor on the class object.
The issue also affects undecorated classes (when targeting older environments; see the examples).
I’m not sure whatexactly triggers the
__setFunctionName()block injection. Sometimes it’s injected even if the class has a correct unambiguous automatic name. Sometimes itisn’t injected, even if the class becomes incorrectly named (e.g., when targetinges3/es5):Missing `__setFunctionName()`
// tests/cases/compiler/blockScopedVariablesUseBeforeDef.tsfunctionfoo8(){lety=class{a=x;};letx;//@ts-ignoreconsole.log(y.name);}// target≤es5 => 'class_###'// target≥es2015 => 'y'foo8();But either way, it shadows non-field static
namedeclarations.
Possible Fix
Atruntime, check that the descriptor ofthis.name matches the automaticname descriptor shape (i.e.,value:string,writable≡enumerable≡false,configurable=true).The runtime check is necessary to correctly handle staticname declared with a computed key.
Technically, the necessary and sufficient condition is even simpler—just check forwritable≡false before__setFunctionName to robustly prevent overriding of non-field staticname declarations:
- At this point, the descriptors are completely defined by the syntax:
- No
static {}blocks evaluated. - No class decorators evaluated.
- No class element initializers evaluated.
- No
- So, the static
nameis one of:- Automatic,always defined. If there’s a static
namefield, it’s to be reconfigured at the time of the actual field initialization. - Method.
get/setoraccessor.
- Automatic,always defined. If there’s a static
- Methods have
writable≡true. get/set/accessordeclarations havewritable≡undefined.
This matches the correct behavior:
- With afield
static namedeclaration, it’s theautomaticnameup to the time of the actualstatic nameinitialization. The field initialization successfully overrides the automatic descriptor. - For non-fields, the descriptor matches the declaration from the very beginning of the class. If the
nameis reconfigured here, the actual declaration doesn’t revert the override.
AdjustcreateClassNamedEvaluationHelperBlock() andisClassNamedEvaluationHelperBlock() innamedEvaluation.ts to produce a code like this:
static{Object.getOwnPropertyDescriptor(this,"name").writable===false&&__setFunctionName(this, ...);}
NB: With
target≤es5anduseDefineForClassFields≡false, the compiler uses plainC.name=...assignment to set the staticname, which has no effect (doesn’t reconfigure the staticnamedescriptor), so the injected block—if injected—will call__setFunctionName()unconditionally. But this is already a compile-time errorStatic property 'name' conflicts with built-in property'Function.name' of constructor function 'A'. (2699)so it doesn’t matter here.