From 8595fa192803d86cd7c84544f31ae562cc353686 Mon Sep 17 00:00:00 2001 From: Eugene Kalinin Date: Mon, 7 Sep 2026 23:12:22 +0300 Subject: [PATCH 1/2] fix(checker): cap template literal type size to avoid unbounded growth Recursive conditional types with an `any` check type keep tail-recursing while their template literal argument doubles on every iteration. The tail recursion limit in getConditionalType is never reached because the string (or the number of placeholders) grows exponentially and exhausts memory first. Bound both the text length and the number of placeholders produced by getTemplateLiteralType and report TS2589 when a limit is exceeded, in the same way checkCrossProductUnion bounds union sizes. Fixes #63271 --- tsc/internal/checker/checker.go | 17 ++++++ ...plateLiteralTypeExcessiveLength.errors.txt | 31 ++++++++++ .../templateLiteralTypeExcessiveLength.js | 31 ++++++++++ ...templateLiteralTypeExcessiveLength.symbols | 58 +++++++++++++++++++ .../templateLiteralTypeExcessiveLength.types | 33 +++++++++++ .../templateLiteralTypeExcessiveLength.ts | 23 ++++++++ 6 files changed, 193 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.js create mode 100644 tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.types create mode 100644 tsc/testdata/tests/cases/compiler/templateLiteralTypeExcessiveLength.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index d19cf6b214b79..24b246dc24872 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -29492,6 +29492,14 @@ func (c *Checker) getConstraintDeclaration(t *Type) *ast.Node { return nil } +// Limits on the size of a template literal type produced by getTemplateLiteralType. Recursive instantiations +// such as `Recur` double the text (or the number of placeholders) on every iteration and +// exhaust memory long before the tail recursion limit in getConditionalType is reached (see #63271). +const ( + maxTemplateLiteralTypeLength = 50_000_000 + maxTemplateLiteralTypeSpans = 100_000 +) + func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { unionIndex := core.FindIndex(types, func(t *Type) bool { return t.flags&(TypeFlagsNever|TypeFlagsUnion) != 0 @@ -29511,6 +29519,7 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { var newTexts []string var sb strings.Builder sb.WriteString(texts[0]) + tooLarge := false var addSpans func([]string, []*Type) bool addSpans = func(texts []string, types []*Type) bool { for i, t := range types { @@ -29532,10 +29541,18 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { default: return false } + if sb.Len() > maxTemplateLiteralTypeLength || len(newTypes) > maxTemplateLiteralTypeSpans { + tooLarge = true + return false + } } return true } if !addSpans(texts, types) { + if tooLarge { + c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite) + return c.errorType + } return c.stringType } if len(newTypes) == 0 { diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.errors.txt b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.errors.txt new file mode 100644 index 0000000000000..5cd9fe2a2c226 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.errors.txt @@ -0,0 +1,31 @@ +templateLiteralTypeExcessiveLength.ts(15,11): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(20,11): error TS2589: Type instantiation is excessively deep and possibly infinite. + + +==== templateLiteralTypeExcessiveLength.ts (2 errors) ==== + // Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and + // keeps tail-recursing forever. The checker must report an error instead of building an unbounded + // template literal type. + + type Dec = + N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 : + N extends 2 ? any : + N extends 1 ? 0 : 0; + + type Recur = + N extends 0 ? S : Recur, `${S}_${S}`>; + + // The string literal doubles on every iteration. + type Explode = { + [P in Recur<5, "a"> as `${P}_key`]: any; + ~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + }; + + // The number of placeholders doubles on every iteration. + type ExplodeSpans = { + [P in Recur<5, string> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + }; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.js b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.js new file mode 100644 index 0000000000000..4aa2f0bd5f776 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.js @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/templateLiteralTypeExcessiveLength.ts] //// + +//// [templateLiteralTypeExcessiveLength.ts] +// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and +// keeps tail-recursing forever. The checker must report an error instead of building an unbounded +// template literal type. + +type Dec = + N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 : + N extends 2 ? any : + N extends 1 ? 0 : 0; + +type Recur = + N extends 0 ? S : Recur, `${S}_${S}`>; + +// The string literal doubles on every iteration. +type Explode = { + [P in Recur<5, "a"> as `${P}_key`]: any; +}; + +// The number of placeholders doubles on every iteration. +type ExplodeSpans = { + [P in Recur<5, string> as `${P}_key`]: any; +}; + + +//// [templateLiteralTypeExcessiveLength.js] +"use strict"; +// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and +// keeps tail-recursing forever. The checker must report an error instead of building an unbounded +// template literal type. diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.symbols b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.symbols new file mode 100644 index 0000000000000..969e1bcf63348 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.symbols @@ -0,0 +1,58 @@ +//// [tests/cases/compiler/templateLiteralTypeExcessiveLength.ts] //// + +=== templateLiteralTypeExcessiveLength.ts === +// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and +// keeps tail-recursing forever. The checker must report an error instead of building an unbounded +// template literal type. + +type Dec = +>Dec : Symbol(Dec, Decl(templateLiteralTypeExcessiveLength.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9)) + + N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 : +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9)) +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9)) +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9)) + + N extends 2 ? any : +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9)) + + N extends 1 ? 0 : 0; +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9)) + +type Recur = +>Recur : Symbol(Recur, Decl(templateLiteralTypeExcessiveLength.ts, 7, 24)) +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 9, 11)) +>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 9, 28)) + + N extends 0 ? S : Recur, `${S}_${S}`>; +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 9, 11)) +>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 9, 28)) +>Recur : Symbol(Recur, Decl(templateLiteralTypeExcessiveLength.ts, 7, 24)) +>Dec : Symbol(Dec, Decl(templateLiteralTypeExcessiveLength.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 9, 11)) +>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 9, 28)) +>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 9, 28)) + +// The string literal doubles on every iteration. +type Explode = { +>Explode : Symbol(Explode, Decl(templateLiteralTypeExcessiveLength.ts, 10, 49)) + + [P in Recur<5, "a"> as `${P}_key`]: any; +>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 14, 5)) +>Recur : Symbol(Recur, Decl(templateLiteralTypeExcessiveLength.ts, 7, 24)) +>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 14, 5)) + +}; + +// The number of placeholders doubles on every iteration. +type ExplodeSpans = { +>ExplodeSpans : Symbol(ExplodeSpans, Decl(templateLiteralTypeExcessiveLength.ts, 15, 2)) + + [P in Recur<5, string> as `${P}_key`]: any; +>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 19, 5)) +>Recur : Symbol(Recur, Decl(templateLiteralTypeExcessiveLength.ts, 7, 24)) +>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 19, 5)) + +}; + diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.types b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.types new file mode 100644 index 0000000000000..f87f996834118 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.types @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/templateLiteralTypeExcessiveLength.ts] //// + +=== templateLiteralTypeExcessiveLength.ts === +// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and +// keeps tail-recursing forever. The checker must report an error instead of building an unbounded +// template literal type. + +type Dec = +>Dec : Dec + + N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 : + N extends 2 ? any : + N extends 1 ? 0 : 0; + +type Recur = +>Recur : Recur + + N extends 0 ? S : Recur, `${S}_${S}`>; + +// The string literal doubles on every iteration. +type Explode = { +>Explode : Explode + + [P in Recur<5, "a"> as `${P}_key`]: any; +}; + +// The number of placeholders doubles on every iteration. +type ExplodeSpans = { +>ExplodeSpans : ExplodeSpans + + [P in Recur<5, string> as `${P}_key`]: any; +}; + diff --git a/tsc/testdata/tests/cases/compiler/templateLiteralTypeExcessiveLength.ts b/tsc/testdata/tests/cases/compiler/templateLiteralTypeExcessiveLength.ts new file mode 100644 index 0000000000000..227128bf996ce --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/templateLiteralTypeExcessiveLength.ts @@ -0,0 +1,23 @@ +// @strict: true + +// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and +// keeps tail-recursing forever. The checker must report an error instead of building an unbounded +// template literal type. + +type Dec = + N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 : + N extends 2 ? any : + N extends 1 ? 0 : 0; + +type Recur = + N extends 0 ? S : Recur, `${S}_${S}`>; + +// The string literal doubles on every iteration. +type Explode = { + [P in Recur<5, "a"> as `${P}_key`]: any; +}; + +// The number of placeholders doubles on every iteration. +type ExplodeSpans = { + [P in Recur<5, string> as `${P}_key`]: any; +}; From 9dbd5939e842994872658eda34c6ddf18a994e3e Mon Sep 17 00:00:00 2001 From: Eugene Kalinin Date: Mon, 7 Sep 2026 23:42:38 +0300 Subject: [PATCH 2/2] fix(checker): count all template literal segments toward the length limit The length guard only measured the current builder segment. Once a generic placeholder moved the completed segment into newTexts and reset the builder, the combined text of a type such as `${S}${string}${S}` was bounded only by the placeholder limit, so the total could still grow to gigabytes before any error was reported. Track the combined length of the segments already moved into newTexts and compare the running total against the limit. --- tsc/internal/checker/checker.go | 4 +- ...plateLiteralTypeExcessiveLength.errors.txt | 19 ++++++- .../templateLiteralTypeExcessiveLength.js | 14 +++++ ...templateLiteralTypeExcessiveLength.symbols | 51 +++++++++++++++++++ .../templateLiteralTypeExcessiveLength.types | 25 +++++++++ .../templateLiteralTypeExcessiveLength.ts | 14 +++++ 6 files changed, 125 insertions(+), 2 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 24b246dc24872..a87cdda29ad99 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -29519,6 +29519,7 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { var newTexts []string var sb strings.Builder sb.WriteString(texts[0]) + textLength := 0 // combined length of the segments already moved into newTexts tooLarge := false var addSpans func([]string, []*Type) bool addSpans = func(texts []string, types []*Type) bool { @@ -29536,12 +29537,13 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { case c.isGenericIndexType(t) || c.isPatternLiteralPlaceholderType(t): newTypes = append(newTypes, t) newTexts = append(newTexts, stringutil.CombineSurrogatePairs(sb.String())) + textLength += sb.Len() sb.Reset() sb.WriteString(texts[i+1]) default: return false } - if sb.Len() > maxTemplateLiteralTypeLength || len(newTypes) > maxTemplateLiteralTypeSpans { + if textLength+sb.Len() > maxTemplateLiteralTypeLength || len(newTypes) > maxTemplateLiteralTypeSpans { tooLarge = true return false } diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.errors.txt b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.errors.txt index 5cd9fe2a2c226..2c10283c820ef 100644 --- a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.errors.txt @@ -1,8 +1,9 @@ templateLiteralTypeExcessiveLength.ts(15,11): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(20,11): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(34,11): error TS2589: Type instantiation is excessively deep and possibly infinite. -==== templateLiteralTypeExcessiveLength.ts (2 errors) ==== +==== templateLiteralTypeExcessiveLength.ts (3 errors) ==== // Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and // keeps tail-recursing forever. The checker must report an error instead of building an unbounded // template literal type. @@ -28,4 +29,20 @@ templateLiteralTypeExcessiveLength.ts(20,11): error TS2589: Type instantiation i ~~~~~~~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; + + // Every text segment stays small, but the number of segments doubles on every iteration, + // so the combined text length grows without bound. + type A16 = "aaaaaaaaaaaaaaaa"; + type A64 = `${A16}${A16}${A16}${A16}`; + type A256 = `${A64}${A64}${A64}${A64}`; + type A1024 = `${A256}${A256}${A256}${A256}`; + + type RecurSegments = + N extends 0 ? S : RecurSegments, `${S}${string}${S}`>; + + type ExplodeSegments = { + [P in RecurSegments<5, A1024> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + }; \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.js b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.js index 4aa2f0bd5f776..36fa92d4a5035 100644 --- a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.js +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.js @@ -22,6 +22,20 @@ type Explode = { type ExplodeSpans = { [P in Recur<5, string> as `${P}_key`]: any; }; + +// Every text segment stays small, but the number of segments doubles on every iteration, +// so the combined text length grows without bound. +type A16 = "aaaaaaaaaaaaaaaa"; +type A64 = `${A16}${A16}${A16}${A16}`; +type A256 = `${A64}${A64}${A64}${A64}`; +type A1024 = `${A256}${A256}${A256}${A256}`; + +type RecurSegments = + N extends 0 ? S : RecurSegments, `${S}${string}${S}`>; + +type ExplodeSegments = { + [P in RecurSegments<5, A1024> as `${P}_key`]: any; +}; //// [templateLiteralTypeExcessiveLength.js] diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.symbols b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.symbols index 969e1bcf63348..2e85049b74921 100644 --- a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.symbols +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.symbols @@ -56,3 +56,54 @@ type ExplodeSpans = { }; +// Every text segment stays small, but the number of segments doubles on every iteration, +// so the combined text length grows without bound. +type A16 = "aaaaaaaaaaaaaaaa"; +>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2)) + +type A64 = `${A16}${A16}${A16}${A16}`; +>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30)) +>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2)) +>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2)) +>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2)) +>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2)) + +type A256 = `${A64}${A64}${A64}${A64}`; +>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38)) +>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30)) +>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30)) +>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30)) +>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30)) + +type A1024 = `${A256}${A256}${A256}${A256}`; +>A1024 : Symbol(A1024, Decl(templateLiteralTypeExcessiveLength.ts, 26, 39)) +>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38)) +>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38)) +>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38)) +>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38)) + +type RecurSegments = +>RecurSegments : Symbol(RecurSegments, Decl(templateLiteralTypeExcessiveLength.ts, 27, 44)) +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 29, 19)) +>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 29, 36)) + + N extends 0 ? S : RecurSegments, `${S}${string}${S}`>; +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 29, 19)) +>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 29, 36)) +>RecurSegments : Symbol(RecurSegments, Decl(templateLiteralTypeExcessiveLength.ts, 27, 44)) +>Dec : Symbol(Dec, Decl(templateLiteralTypeExcessiveLength.ts, 0, 0)) +>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 29, 19)) +>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 29, 36)) +>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 29, 36)) + +type ExplodeSegments = { +>ExplodeSegments : Symbol(ExplodeSegments, Decl(templateLiteralTypeExcessiveLength.ts, 30, 65)) + + [P in RecurSegments<5, A1024> as `${P}_key`]: any; +>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 33, 5)) +>RecurSegments : Symbol(RecurSegments, Decl(templateLiteralTypeExcessiveLength.ts, 27, 44)) +>A1024 : Symbol(A1024, Decl(templateLiteralTypeExcessiveLength.ts, 26, 39)) +>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 33, 5)) + +}; + diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.types b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.types index f87f996834118..e882332afbf78 100644 --- a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.types +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength.types @@ -31,3 +31,28 @@ type ExplodeSpans = { [P in Recur<5, string> as `${P}_key`]: any; }; +// Every text segment stays small, but the number of segments doubles on every iteration, +// so the combined text length grows without bound. +type A16 = "aaaaaaaaaaaaaaaa"; +>A16 : "aaaaaaaaaaaaaaaa" + +type A64 = `${A16}${A16}${A16}${A16}`; +>A64 : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +type A256 = `${A64}${A64}${A64}${A64}`; +>A256 : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +type A1024 = `${A256}${A256}${A256}${A256}`; +>A1024 : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +type RecurSegments = +>RecurSegments : RecurSegments + + N extends 0 ? S : RecurSegments, `${S}${string}${S}`>; + +type ExplodeSegments = { +>ExplodeSegments : ExplodeSegments + + [P in RecurSegments<5, A1024> as `${P}_key`]: any; +}; + diff --git a/tsc/testdata/tests/cases/compiler/templateLiteralTypeExcessiveLength.ts b/tsc/testdata/tests/cases/compiler/templateLiteralTypeExcessiveLength.ts index 227128bf996ce..eeb573c01c164 100644 --- a/tsc/testdata/tests/cases/compiler/templateLiteralTypeExcessiveLength.ts +++ b/tsc/testdata/tests/cases/compiler/templateLiteralTypeExcessiveLength.ts @@ -21,3 +21,17 @@ type Explode = { type ExplodeSpans = { [P in Recur<5, string> as `${P}_key`]: any; }; + +// Every text segment stays small, but the number of segments doubles on every iteration, +// so the combined text length grows without bound. +type A16 = "aaaaaaaaaaaaaaaa"; +type A64 = `${A16}${A16}${A16}${A16}`; +type A256 = `${A64}${A64}${A64}${A64}`; +type A1024 = `${A256}${A256}${A256}${A256}`; + +type RecurSegments = + N extends 0 ? S : RecurSegments, `${S}${string}${S}`>; + +type ExplodeSegments = { + [P in RecurSegments<5, A1024> as `${P}_key`]: any; +};