microsoft / microsoft/TypeScript

`importHelpers` incorrectly requires `tslib` for native `#private` class members at every dated `target` (ES2022–ES2025), even though no helper is ever emitted

Offen
#63,728 3 Kommentare 1 Reaktion 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Bug Domain: tslib and Helper Functions Help Wanted
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.3k
Ø Merge
2 T. 4 Std.
Gemergte PRs (30 T.)
132

Beschreibung

### 🔎 Search Terms

TS2354, "requires an imported helper", tslib private field, importHelpers ES2022, classPrivateFieldGet ES2022, classPrivateFieldSet ES2022, classPrivateFieldIn, ClassAndClassElementDecorators, LanguageFeatureMinimumTarget, private field tslib not needed, importHelpers private field false positive, private field decorator target ESNext

### 🕗 Version & Regression Information

- This is the behavior in every version I tried, and I reviewed the FAQ (no entries about `tslib`, `importHelpers`, or private fields exist there).
- Reproduces on every TypeScript version from **4.3.5** (when native `#private` fields and the `ES2022` target were introduced) through the current **7.0.2** and the **7.1.0-dev** nightly — i.e. every version able to target `ES2022`.
- Not a regression introduced at any specific version, but the *mechanism* changed once, without changing the observable behavior: TS 4.3.5 through 5.4.5 used a blunt, unconditional `if (languageVersion < ScriptTarget.ESNext)` guard around the private-field helper check. TS 5.5.2 refactored this into today's three-term `LanguageFeatureMinimumTarget`-based condition (see "Additional information" below) — but for private fields specifically, the observable result is identical in every version: every dated `target` requires `tslib`; only the literal string `"ESNext"` doesn't.
- I was unable to reproduce this in the Playground specifically (see below), so I could not bisect via the Playground's version picker; I instead bisected locally by installing each `typescript@x.y.z` from npm and running `tsc` directly (versions tested: 4.3.5, 4.5.5, 4.7.4, 4.9.5, 5.0.4, 5.1.6, 5.2.2, 5.3.3, 5.4.5, 5.5.4, 5.6.3, 5.7.3, 5.8.3, 5.9.3, 6.0.3, 7.0.2, 7.1.0-dev — all reproduce it identically).

### ⏯ Playground Link

https://github.com/astegmaier/typescript-private-field-tslib-repro (you have to do this locally because the playground always has tslib installed)

### 💻 Code

```ts
// tsconfig.json: { "target": "ES2022", "importHelpers": true }
// no tslib installed in node_modules

export class C {
#x = 0;
get(): number { return this.#x; } // needs __classPrivateFieldGet per tsc
set(v: number): void { this.#x = v; } // needs __classPrivateFieldSet per tsc
has(o: unknown): boolean { return #x in (o as C); } // needs __classPrivateFieldIn per tsc
}
```

### 🙁 Actual behavior

With `target: "ES2022"` (or any dated target through `"ES2025"`) and `importHelpers: true`, and no `tslib` installed, `tsc` reports:

```
error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found.
```

on the very first private-field access it checks (`get()`, above — commenting that method out reveals `set()` fails identically, and commenting that out too reveals `has()` fails identically; `tsc` only ever reports the first one per file). This happens for *every* form of native `#private` member access I could find: field reads/writes, compound assignment, optional chaining, brand checks (`#x in obj`), private methods, private accessors, static private fields/methods, static blocks, and private fields inside class expressions, generic classes, subclasses, and closures.

Once `tslib` **is** installed, the error goes away — but the compiled output for every one of the constructs above contains **zero** references to `tslib`, at any target. I confirmed this by forcing full (non-declaration-only) emission with `tslib` present and installed, and grepping the output `.js` for `tslib`: no matches, for any of the 13 private-field-related constructs listed above, at `ES2022` through `ES2025`.

### 🙂 Expected behavior

`tsc` should not require `tslib` to be resolvable for constructs that are natively supported at the configured `target` and that the emitter never actually references, the same way it doesn't for e.g. arrow functions or `let`/`const` at `ES2022`.

### Additional information about the issue

**Root cause.** In `checker.ts`'s `checkPropertyAccessExpressionOrQualifiedName` (and the parallel check in `checkInExpression` for `#x in obj`), the private-field helper requirement is gated by:

```ts
if (isPrivateIdentifier(right)) {
if (
languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks ||
languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators ||
!useDefineForClassFields
) {
// ...checkExternalEmitHelpers(..., ClassPrivateFieldGet / ClassPrivateFieldSet)
}
```

and in `types.ts`:

```ts
export const LanguageFeatureMinimumTarget: Record = {
PrivateNamesAndClassStaticBlocks: ScriptTarget.ES2022, // correct: native since ES2022
ClassAndClassElementDecorators: ScriptTarget.ESNext, // <- pinned to the ESNext sentinel forever
...
};
```

`ClassAndClassElementDecorators` is pinned to `ScriptTarget.ESNext` (`99`) because TC39 decorators have never been assigned to a dated ECMAScript edition. So `languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators` is `true` for **every** dated target — making the whole `||` condition unconditionally true regardless of `target`, regardless of whether the file uses decorators at all, and regardless of `useDefineForClassFields`. Only the literal target string `"ESNext"` clears that term. This appears to be an unintentional side effect of gating an unrelated feature's (decorators') native-support threshold together with private fields' (correctly-gated-at-ES2022) one, rather than a deliberate design choice.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne in checker.ts bei checkPropertyAccessExpressionOrQualifiedName und der entsprechenden checkInExpression-Prüfung und untersuche anschließend LanguageFeatureMinimumTarget in types.ts. Reproduziere das Problem mit dem verknüpften TypeScript-Beispiel für private Felder unter Verwendung eines ES2022-Ziels und importHelpers. Als abgeschlossen gilt die Änderung, wenn der native Zugriff auf private Member keinen fehlenden tslib-Helfer mehr meldet, sobald die erzeugte Ausgabe keine tslib-Referenz enthält, und eine Regressionstestabdeckung für die relevanten Fälle vorhanden ist.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
typescript
Bereich
compilers
Issue-Typ
Bug
Schwierigkeit
3/5
Geschätzter Aufwand
1-2 Tage
Aktivitätsstatus
Aktiv
Klarheit
Klar beschrieben
Anfängerfreundlichkeit
72/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.