7.1 regression: comparing a recursive `-?` mapped type is quadratic in the number of properties and hits TS2589 (since typescript-go#4820)

Offen
#64,950 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Anfängerfreundlichkeit
48/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
go, typescript

Rechercherichtung

Start by reading the variance computation change in microsoft/typescript-go#4820, then reproduce the issue with the supplied one-file repro and tsc -p . --singleThreaded --extendedDiagnostics. Compare instantiation counts for the last good and first bad builds, focusing on the recursive Builder interface, its -? mapped type, and changing S | K argument. Done means the comparison stays near linear in property count and the 500-property repro succeeds without TS2589.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

Bug
🔎 Search Terms

variance, TS2589, excessively deep, instantiation count, mapped type, -?, quadratic, 7.1 regression

🕗 Version & Regression Information
  • Last good: 7.1.0-dev.20260805.1 (gitHead 12318e599d)
  • First bad: 7.1.0-dev.20260806.1 (gitHead 86cc4767d4)
  • That range is one commit: microsoft/typescript-go#4820, "Order variance computation by associated type symbol".
  • Still failing on 7.1.0-beta and 7.1.0-dev.20261024.1.
  • 7.0.2 accepts the code.

Same commit as #64891 and #64930, but this one is about performance: the comparison finishes with a correct result
for small types, it just costs quadratically more instantiations, and on real types it hits the 5M cap.

⏯ Playground Link

The 7.1 builds aren't available in the Playground. CLI repro: one file in an empty directory, tsc -p . --singleThreaded --extendedDiagnostics.

tsconfig.json

{ "compilerOptions": { "strict": true, "noEmit": true, "types": [] }, "files": ["repro.ts"] }
💻 Code
type D = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type Entity = Record<`f${'0' | '1' | '2' | '3' | '4'}${D}${D}`, string> // 500 properties

interface Method<T, K extends keyof T, S> {
	(): Builder<T, S | K>
}
type Builder<T, S = never> = { [K in keyof T]-?: Method<T, K, S> }

declare const builder: Builder<Entity>
export const widened: Builder<Entity & object> = builder
🙁 Actual behavior
repro.ts(10,14): error TS2589: Type instantiation is excessively deep and possibly infinite.
Instantiations: 5003892   Check time: 79 s
🙂 Expected behavior

No error, as in 7.0.2 and 7.1.0-dev.20260805.1: about 9k instantiations, 0.04 s check time.

Additional information about the issue

With 100 properties (Record<`f${D}${D}`, string>) there is no error, but the count goes from 5,105 to 256,939
instantiations. It grows with the square of the property count: about 66k at 50 properties, 1M at 200 and the 5M cap
near 460.

build 100 props: instantiations 500 props
7.0.2 5,105 9,105, ok
7.1.0-dev.20260805.1 (12318e599d) 5,105 9,105, ok
7.1.0-dev.20260806.1 (86cc4767d4) 256,939 TS2589
7.1.0-dev.20261024.1 258,143 TS2589

All three of these are needed for the quadratic count; removing any one brings it back to linear:

  • the -? modifier on the homomorphic mapped type;
  • Method as a generic interface that references Builder again (with an inline () => Builder<T, S | K> both
    7.0.2 and 7.1 are quadratic, so the regression is in how the interface's variance gets computed);
  • a type argument that actually changes, here S | K (with Builder<T, S> it stays linear).

The target also has to be a different but equivalent instantiation (Entity & object); a structurally identical
mapped copy of Entity does not trigger it.

Real-world impact: this is a reduced form of a fluent "selection builder" type from our library, whose
entity types resolve through UnionToIntersection<E> & object, so every selector is compared against
Builder<E & object>. On a ~2,100-file app under 7.1:

  • TS2589 appears on ordinary selector call sites, and the callbacks' destructured parameters cascade into TS7031/TS7006.
  • The depth error leaves the checker's caches in a bad state, so a later file that checks cleanly on its own reports
    TS2339 (members missing on the builder's result type). The error count depends on --checkers and on which files
    share a checker (0 to 77).
  • The project checks --singleThreaded in 25 s / 2.6 GB on 7.1.0-dev.20260805.1; on 7.1.0-dev.20260930.4 the
    same run was OOM-killed at an 18 GB cap, and with default checkers it reached 24 GB before being killed.
Vorherrschende Sprache
Go
Sterne
112k
Forks
15.4k
Ø Merge
3 T. 1 Std.
Gemergte PRs (30 T.)
136

Entwicklungsumgebung

In Codespaces öffnen

Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus microsoft/TypeScript

Alle Issues in microsoft/TypeScript

Ähnliche Issues

Weitere Issues zu Go

Neue Issues direkt in Ihr Postfach

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