microsoft / microsoft/TypeScript
`__setFunctionName` breaks custom static `name` on classes
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Go
- Sterne
- 111k
- Forks
- 14.3k
- Ø Merge
- 2 T. 4 Std.
- Gemergte PRs (30 T.)
- 132
Beschreibung
### 🔎 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.
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Beginne in namedEvaluation.ts mit createClassNamedEvaluationHelperBlock() und isClassNamedEvaluationHelperBlock(), reproduziere anschließend die bereitgestellten TypeScript-Beispiele im Playground und untersuche das generierte JavaScript. Als erledigt gilt die Aufgabe, wenn dekorierte und nicht dekorierte Klassen statische Namensdeklarationen, die keine Felder sind, bewahren und die automatische Klassennamensgebung weiterhin funktioniert.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- javascript, typescript
- Bereich
- compilers
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Veraltet
- Klarheit
- Klar beschrieben
- Anfängerfreundlichkeit
- 42/100