Skip combined-constraint check while measuring variances to avoid unbounded chase - #64528
Alexis Delrieu (Amatewasu) wants to merge 1 commit into
Conversation
The combined-constraint comparison in `structuredTypeRelatedTo` hoists the constraints of the instantiable constituents of an intersection source into a combined constraint and compares that constraint against the target. While measuring variances, the hoisted constraint may itself be an intersection with instantiable constituents (e.g. deferred conditional types, as produced by heavily overloaded generic builder types), so a failed comparison re-enters the same logic with a structurally new and ever larger source that neither the relation cache nor the deeply-nested-type guards recognize as recursive, and the chase grows without bound until the process runs out of memory. Skip the check while the variance stack is non-empty; it never contributes to the measured relation between the marker types. Fixes microsoft#64423
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The compiler fix lacks a checked-in regression test for the unbounded-chase scenario.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Prevents unbounded type-relation expansion during variance measurement.
Changes:
- Skips combined-constraint checks while the variance stack is active.
- Documents the recursive growth scenario.
| File | Description |
|---|---|
tsc/internal/checker/relater.go |
Guards combined-constraint evaluation during variance measurement. |
| // that the relation cache and the deeply-nested-type guards never recognize as | ||
| // recursive, and the chase grows without bound (see microsoft/TypeScript#64423). | ||
| // The check never contributes to the measured relation between the marker types. | ||
| if result == TernaryFalse && len(r.c.varianceStack) == 0 && (source.flags&TypeFlagsIntersection != 0 || source.flags&TypeFlagsTypeParameter != 0 && target.flags&TypeFlagsUnion != 0) { |
Verified against the private project from the issueI ran the full typecheck of the actual project behind #64423 (a private React 19 + R3F + TSX app, ~855 files) with binaries built from this PR branch vs. unpatched
Diagnostics parity holds: the patched TS 7 reports exactly the same result as TS 6 (zero errors) on the full project, so the guard is not observable in output — it only stops the runaway. Caveats, for completeness:
|

Fixes #64423
Problem
TypeScript 7 runs out of memory (unbounded growth, zero diagnostics) on programs that TypeScript 6 checks fine. Root cause: the combined-constraint comparison in
structuredTypeRelatedTohoists the constraints of the instantiable constituents of an intersection source into a combined constraint and compares it against the target. While measuring variances, the hoisted constraint may itself be an intersection with instantiable constituents (e.g. deferred conditional types produced by heavily overloaded generic builder types), so a failed comparison re-enters the same logic with a structurally new and ever larger source that neither the relation cache nor the deeply-nested-type guards recognize as recursive — the chase grows without bound until the process dies.Fix
Skip the combined-constraint check while the variance stack is non-empty; it never contributes to the measured relation between the marker types.
Verification (both repros from the issue)
@react-three/fiberIntrinsicElementsaugmentation +<SvgIcon />, exact versions from the issue): before — killed at a 3 GiB / 6 GiB address-space cap with zero output; after — ~4.4 s, ~700 MB RSS, exit 0.three/tslrepro (uv().div(vec2(1, 1)), three@0.185.1 + @types/three@0.185.1): before — killed at the cap with zero output; after — ~3.1 s, ~280 MB RSS, exit 0.go test ./...passes (only pre-existing environmental failure ininternal/astnav, which requires a locally built JStypescriptpackage in rootnode_modules; fails identically on unpatchedmain).Note on the approach
An earlier variant of this change additionally capped the chase depth (a counter on
Relaterlimiting nested combined-constraint comparisons to 3). That cap regressedtests/cases/compiler/indexedAccessAndNullableNarrowing.ts(new false TS2345 errors on thesyncStoreProppattern from #57693, which both TS 6 and unpatched TS 7 accept), and the depth needed by legitimate code is not bounded by any small constant, so the cap was dropped. The variance-stack guard alone fixes both repros.Disclosure
This change was generated by an LLM (as was the investigation in the issue thread).
Checklist notes
npx hereby lint/check:format(no rootnode_modulesin this checkout); rangofmt+go vet(clean) and the full Go test suite instead.mainbranchgo test ./...passesnpx hereby lintnpx hereby check:format