microsoft / microsoft/TypeScript
`__setFunctionName` breaks custom static `name` on classes
Personne n'a encore pris cette issue.
- Langage dominant
- Go
- Étoiles
- 111k
- Forks
- 14.3k
- Merge moyen
- 2 j 4 h
- PR mergées (30 j)
- 132
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
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.
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Commencez dans namedEvaluation.ts avec createClassNamedEvaluationHelperBlock() et isClassNamedEvaluationHelperBlock(), puis reproduisez les exemples TypeScript fournis dans le playground et examinez le JavaScript généré. Le travail est terminé lorsque les classes décorées et non décorées préservent les déclarations de nom statiques qui ne sont pas des champs, tout en permettant au nommage automatique des classes de continuer à fonctionner.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- javascript, typescript
- Domaine
- compilers
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- À l'abandon
- Clarté
- Clairement spécifiée
- Accessibilité débutants
- 42/100