microsoft / microsoft/TypeScript
`__setFunctionName` breaks custom static `name` on classes
还没有人认领这个 Issue。
- 主要语言
- Go
- 星标
- 111k
- 派生
- 14.3k
- 平均合并
- 2 天 4 小时
- 30 天内合并 PR
- 132
描述
### 🔎 Search Terms
__setFunctionName
class static name
ES decorators static name
__esDecorate static name
### 🕗 Version & Regression Information
5.0.4-5.9.3
### ⏯ Playground Link
https://tsplay.dev/WJgblw
### 💻 Code
```ts
const noop = (Self: any, ctx: ClassDecoratorContext) => {};
const rand = () => 4;
@noop export default class {
static get name() { return 2434; }
}
(@noop class {
static get name() { return 2434; }
});
(@noop class __A {
static get name() { return 2434; }
});
@noop
class __B {
static get name() { return 2434; }
}
var __C = @noop class {
static get name() { return 2434; }
};
var __D = {
[rand()]: @noop class {
static get name() { return 2434; }
}
};
// Unconditionally throws at runtime:
@noop class __E {
static #a = 2434;
static get name() { return this.#a; }
}
// Undecorated classes are broken as well:
var __F = class {
static get name() { return 2434; }
// target≤es2021 => '__F'
// target≥es2022 => 2434
static {}
}
```
### 🙁 Actual behavior
Static `name` is overridden by `__setFunctionName()`.
### 🙂 Expected behavior
Static `name` should work as defined in the class code.
### Additional information about the issue
Generated code for the ES decorators breaks custom static `name` property on the decorated class. The only syntax that works is plain `static name;` field declaration; other kinds (methods, `get`/`set`/`accessor`) are broken.
The issue reproduces iff the compiler decides to inject the `static { __setFunctionName(this, ...) }` block, which _unconditionally_ overrides the static `name` descriptor on the class object.
The issue also affects undecorated classes (when targeting older environments; see the examples).
> I’m not sure what _exactly_ triggers the `__setFunctionName()` block injection. Sometimes it’s injected even if the class has a correct unambiguous automatic name. Sometimes it **isn’t** injected, even if the class becomes incorrectly named (e.g., when targeting `es3`/`es5`):
>
>
> Missing `__setFunctionName()`
>
> https://tsplay.dev/WKYAMN
>
> ```typescript
> // tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts
> function foo8() {
> let y = class {
> a = x;
> };
> let x;
>
> // @ts-ignore
> console.log(y.name);
> }
>
> // target≤es5 => 'class_###'
> // target≥es2015 => 'y'
> foo8();
> ```
>
>
>
> But either way, it shadows non-field static `name` declarations.
## Possible Fix
At _runtime_, check that the descriptor of `this.name` matches the automatic `name` descriptor shape (i.e., `value:string`, `writable≡enumerable≡false`, `configurable=true`). _The runtime check is necessary to correctly handle static `name` declared with a computed key._
Technically, the necessary and sufficient condition is even simpler—just check for `writable≡false` before `__setFunctionName` to robustly prevent overriding of non-field static `name` 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.
- So, the static `name` is one of:
- Automatic, _always_ defined. If there’s a static `name` _field_, it’s to be reconfigured at the time of the actual field initialization.
- Method.
- `get`/`set` or `accessor`.
- Methods have `writable≡true`.
- `get`/`set`/`accessor` declarations have `writable≡undefined`.
This matches the correct behavior:
- With a _field_ `static name` declaration, it’s the _automatic_ `name` up to the time of the actual `static name` initialization. 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 `name` is reconfigured here, the actual declaration doesn’t revert the override.
Adjust `createClassNamedEvaluationHelperBlock()` and `isClassNamedEvaluationHelperBlock()` in `namedEvaluation.ts` to produce a code like this:
```typescript
static {
Object.getOwnPropertyDescriptor(this, "name").writable === false &&
__setFunctionName(this, ...);
}
```
> **NB:** With `target≤es5` and `useDefineForClassFields≡false`, the compiler uses plain `C.name=...` assignment to set the static `name`, which has no effect (doesn’t reconfigure the static `name` descriptor), so the injected block—if injected—will call `__setFunctionName()` _unconditionally_. But this is already a compile-time error
>
> ```
> Static property 'name' conflicts with built-in property
> 'Function.name' of constructor function 'A'. (2699)
> ```
>
> so it doesn’t matter here.
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
从 namedEvaluation.ts 中的 createClassNamedEvaluationHelperBlock() 和 isClassNamedEvaluationHelperBlock() 开始,然后在 playground 中复现所提供的 TypeScript 示例,并检查生成的 JavaScript。完成的标准是:带装饰器和不带装饰器的类都保留非字段的静态名称声明,同时自动类命名仍然有效。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- javascript, typescript
- 领域
- compilers
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 停滞
- 描述清晰度
- 描述清楚
- 新手友好度
- 42/100