Skip to content

Fix integer overflow in getCrossProductUnionSize causing hang on large template literal types#3929

Open
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-tsgo-hang-large-template-types
Open

Fix integer overflow in getCrossProductUnionSize causing hang on large template literal types#3929
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-tsgo-hang-large-template-types

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 16, 2026

Fixes #1346

Analysis

When getCrossProductUnionSize computes the cross product of many union types (e.g., 49 unions of 4 members each = 4^49), the result overflows Go's int (signed 64-bit integer). Specifically, 4^32 = 2^64 wraps to 0 in signed 64-bit arithmetic, causing all subsequent multiplications to produce 0. This means getCrossProductUnionSize returns 0, which is less than the 100,000 limit, so checkCrossProductUnion incorrectly allows the expansion to proceed. The recursive mapType calls then attempt to expand 4^49 combinations, effectively hanging.

In TypeScript (the reference implementation), numbers are 64-bit floats, so 4^49 evaluates to Infinity, which correctly triggers the "too complex" error.

type N = 0 | 1 | 2 | 3;
type T = `${N}${N}${N}...${N}`; // 49 unions of 4 → 4^49 combinations → hangs in tsgo

Fix

Added overflow protection in getCrossProductUnionSize: before each multiplication, check whether size > math.MaxInt / n. If so, return math.MaxInt immediately instead of continuing to multiply and risking overflow. This ensures the limit check in checkCrossProductUnion correctly detects and rejects excessively large cross products.

Copilot Checklist

I successfully ran these commands at the end of my session, and they completed without error:

  • npx hereby build
  • npx hereby test
  • npx hereby lint
  • npx hereby format

…e template literal types

Add overflow protection to prevent int64 wraparound when computing cross
product union sizes. When the size would exceed 100,000, return 100,000
immediately instead of continuing to multiply, which could overflow and
produce a value that bypasses the limit check.

Add test case for template literal type with combinatorial explosion.

Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/5fcc3e3e-691e-43a4-b71c-72ae84389f8b

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix tsgo hang on large template literal types Fix integer overflow in getCrossProductUnionSize causing hang on large template literal types May 16, 2026
Copilot AI requested a review from jakebailey May 16, 2026 20:29
Comment thread internal/checker/checker.go Outdated
case t.flags&TypeFlagsUnion != 0:
size *= len(t.Types())
n := len(t.Types())
if n > 0 && size > 100_000/n {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot 100_000 seems like a magic number, and this definitely requires a comment. Probably also best to return MaxInt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in a446575 — now returns math.MaxInt and added a comment explaining the overflow guard and why it's needed (Go int wraps vs JS Infinity).

Copilot AI requested a review from jakebailey May 16, 2026 21:10
@jakebailey jakebailey marked this pull request as ready for review May 16, 2026 22:21
Copilot AI review requested due to automatic review settings May 16, 2026 22:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a hang in the type checker when expanding extremely large template literal types by preventing int overflow in getCrossProductUnionSize, ensuring the existing “too complex” guard (TS2590) reliably triggers.

Changes:

  • Add overflow-capping logic to Checker.getCrossProductUnionSize so cross-product size never wraps to 0/negative on large unions.
  • Add a new compiler test that previously caused a combinatorial explosion hang, now expected to produce TS2590.
  • Add reference baselines for the new test.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/checker/checker.go Caps cross-product union size computation before int overflow to preserve complexity-limit behavior.
testdata/tests/cases/compiler/templateLiteralTypeTooComplex.ts New regression test for large template literal type expansion.
testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.errors.txt Baseline asserting TS2590 is produced.
testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.types Baseline for type output under the new test.
testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.symbols Baseline for symbol output under the new test.
testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.js Baseline for JS emit output under the new test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tsgo hangs on large template literal types with combinatorial explosion

3 participants