From 27bbd7f8a63f6e7b4125a022bc859d7a6e08de3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 19:42:25 +0000 Subject: [PATCH 1/3] Initial plan From d7c783da368d05572e23464707bdcd5083da5ec5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 20:20:28 +0000 Subject: [PATCH 2/3] Fix integer overflow in getCrossProductUnionSize causing hang on large 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> --- internal/checker/checker.go | 6 +- .../templateLiteralTypeTooComplex.errors.txt | 10 ++++ .../compiler/templateLiteralTypeTooComplex.js | 10 ++++ .../templateLiteralTypeTooComplex.symbols | 59 +++++++++++++++++++ .../templateLiteralTypeTooComplex.types | 10 ++++ .../compiler/templateLiteralTypeTooComplex.ts | 5 ++ 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.errors.txt create mode 100644 testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.js create mode 100644 testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.symbols create mode 100644 testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.types create mode 100644 testdata/tests/cases/compiler/templateLiteralTypeTooComplex.ts diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 020fe61193..1b2175456a 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -26447,7 +26447,11 @@ func (c *Checker) getCrossProductUnionSize(types []*Type) int { for _, t := range types { switch { case t.flags&TypeFlagsUnion != 0: - size *= len(t.Types()) + n := len(t.Types()) + if n > 0 && size > 100_000/n { + return 100_000 + } + size *= n case t.flags&TypeFlagsNever != 0: return 0 } diff --git a/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.errors.txt b/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.errors.txt new file mode 100644 index 0000000000..9c3f8ba286 --- /dev/null +++ b/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.errors.txt @@ -0,0 +1,10 @@ +templateLiteralTypeTooComplex.ts(3,10): error TS2590: Expression produces a union type that is too complex to represent. + + +==== templateLiteralTypeTooComplex.ts (1 errors) ==== + // Large template literal types with combinatorial explosion should produce an error, not hang. + type N = 0 | 1 | 2 | 3; + type T = `${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}`; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2590: Expression produces a union type that is too complex to represent. + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.js b/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.js new file mode 100644 index 0000000000..56d95ddb74 --- /dev/null +++ b/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.js @@ -0,0 +1,10 @@ +//// [tests/cases/compiler/templateLiteralTypeTooComplex.ts] //// + +//// [templateLiteralTypeTooComplex.ts] +// Large template literal types with combinatorial explosion should produce an error, not hang. +type N = 0 | 1 | 2 | 3; +type T = `${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}`; + + +//// [templateLiteralTypeTooComplex.js] +"use strict"; diff --git a/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.symbols b/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.symbols new file mode 100644 index 0000000000..6b9ef9e007 --- /dev/null +++ b/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.symbols @@ -0,0 +1,59 @@ +//// [tests/cases/compiler/templateLiteralTypeTooComplex.ts] //// + +=== templateLiteralTypeTooComplex.ts === +// Large template literal types with combinatorial explosion should produce an error, not hang. +type N = 0 | 1 | 2 | 3; +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) + +type T = `${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}`; +>T : Symbol(T, Decl(templateLiteralTypeTooComplex.ts, 1, 23)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeTooComplex.ts, 0, 0)) + diff --git a/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.types b/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.types new file mode 100644 index 0000000000..ef80420132 --- /dev/null +++ b/testdata/baselines/reference/compiler/templateLiteralTypeTooComplex.types @@ -0,0 +1,10 @@ +//// [tests/cases/compiler/templateLiteralTypeTooComplex.ts] //// + +=== templateLiteralTypeTooComplex.ts === +// Large template literal types with combinatorial explosion should produce an error, not hang. +type N = 0 | 1 | 2 | 3; +>N : N + +type T = `${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}`; +>T : any + diff --git a/testdata/tests/cases/compiler/templateLiteralTypeTooComplex.ts b/testdata/tests/cases/compiler/templateLiteralTypeTooComplex.ts new file mode 100644 index 0000000000..e1569f9d4f --- /dev/null +++ b/testdata/tests/cases/compiler/templateLiteralTypeTooComplex.ts @@ -0,0 +1,5 @@ +// @strict: true + +// Large template literal types with combinatorial explosion should produce an error, not hang. +type N = 0 | 1 | 2 | 3; +type T = `${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}${N}`; From a446575fc63f4d3c7f24f4656a6ebba18ecc7cc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 21:10:08 +0000 Subject: [PATCH 3/3] Use math.MaxInt and add comment for overflow guard in getCrossProductUnionSize Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/4f78a0b2-27bb-4eac-98cd-adc2beb086c3 Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/checker/checker.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 1b2175456a..ac40693755 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -26448,8 +26448,11 @@ func (c *Checker) getCrossProductUnionSize(types []*Type) int { switch { case t.flags&TypeFlagsUnion != 0: n := len(t.Types()) - if n > 0 && size > 100_000/n { - return 100_000 + // Cap the result to avoid integer overflow when computing the cross product of many large unions. + // In TypeScript, number overflow produces Infinity which naturally exceeds the limit check; + // in Go, we must guard against int wrapping to zero or negative. + if n > 0 && size > math.MaxInt/n { + return math.MaxInt } size *= n case t.flags&TypeFlagsNever != 0: