From c7bc2c0004634a9e5c673e15ddd7fe3c66e7c7c5 Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Chauhan Date: Wed, 4 Nov 2026 14:38:19 +0530 Subject: [PATCH 1/2] Stop unbounded memory growth after type complexity limits are hit Once a statement exceeded the 5M instantiation limit (TS2589), the checker kept doing the work the limit is meant to prevent: - instantiateType allocated a new TS2589 diagnostic for every further instantiation (millions), only for the diagnostics collection to discard them as duplicates. - Base constraint computation for generic template literal types kept expanding cross products of large unions into string literal unions, interning millions of literal types that can never be used. Report the instantiation limit error once per node. Once it has been reported for the current node, skip expanding the base constraint of generic template literal types (returning the error type, as instantiateType already does). This is limited to base constraint computation, which only runs on generic types, so the error type is not cached for independent non-generic aliases. Also stop expanding remaining union constituents in getTemplateLiteralType as soon as one yields the error type. On the repro from #65095 (next-intl, missing namespace + ICU args), peak RSS at 1,000 messages drops from ~2.9 GB to ~180 MB, and at 14,000 messages it is ~460 MB. The reported diagnostics are unchanged. Fixes microsoft/TypeScript#65095 --- tsc/internal/checker/checker.go | 41 ++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 80a2afdf5d6b7..cd225ecc8edd0 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -605,6 +605,7 @@ type Checker struct { SignatureCount uint32 TotalInstantiationCount uint32 instantiationCount uint32 + instantiationLimitReportedNode *ast.Node // node for which the instantiation count limit error was already reported memberLayoutCount uint32 instantiationStack []*Type conditionalConstraintDepth uint32 @@ -22760,6 +22761,9 @@ func (c *Checker) symbolIsValueEx(symbol *ast.Symbol, includeTypeOnlyMembers boo c.getSymbolFlagsEx(symbol, !includeTypeOnlyMembers, false /*excludeLocalMeanings*/)&ast.SymbolFlagsValue != 0 } +// The maximum number of type instantiations performed for a single statement or expression. +const maxInstantiationCount = 5_000_000 + func (c *Checker) instantiateType(t *Type, m *TypeMapper) *Type { return c.instantiateTypeWithAlias(t, m, nil /*alias*/) } @@ -22771,13 +22775,21 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl if t == nil || m == nil || !(c.couldContainTypeVariables(t) || (t.alias != nil && len(t.alias.typeArguments) > 0 && core.Some(t.alias.typeArguments, c.couldContainTypeVariables))) { return t } - if len(c.instantiationStack) == 100 || c.instantiationCount >= 5_000_000 { + if len(c.instantiationStack) == 100 || c.instantiationCount >= maxInstantiationCount { // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement // or expression. There is a very high likelihood we're dealing with a combination of infinite generic types // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. if tr := c.tracer; tr != nil { tr.Instant(tracing.PhaseCheckTypes, "instantiateType_DepthLimit", map[string]any{"typeId": t.id, "instantiationDepth": len(c.instantiationStack), "instantiationCount": c.instantiationCount}) } + if c.instantiationCount >= maxInstantiationCount { + // Once the count limit is exceeded every further instantiation for this node hits the limit again. + // Report the error only once per node instead of allocating millions of identical diagnostics. + if c.instantiationLimitReportedNode == c.currentNode && c.currentNode != nil { + return c.errorType + } + c.instantiationLimitReportedNode = c.currentNode + } circularTypeNames := c.getCircularTypeNames() switch { case len(circularTypeNames) == 1: @@ -28308,6 +28320,14 @@ func (c *Checker) computeBaseConstraint(t *Type, stack []RecursionId) *Type { } return c.stringNumberSymbolType case t.flags&TypeFlagsTemplateLiteral != 0: + if c.instantiationLimitReportedNode != nil && c.instantiationLimitReportedNode == c.currentNode && c.instantiationCount >= maxInstantiationCount { + // The instantiation limit has already been reported (TS2589) for the current node, and all further + // instantiations yield the error type. Expanding the constraint of this generic template literal type + // into a cross product of string literals would only allocate huge unions that can never be used. + // This is scoped to base constraint computation, which is only ever performed for generic types, so + // the error type is not cached for independently declared (non-generic) aliases. + return c.errorType + } types := t.Types() constraints := make([]*Type, 0, len(types)) for _, s := range types { @@ -29956,9 +29976,24 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { if !c.checkCrossProductUnion(types) { return c.errorType } - return c.mapType(types[unionIndex], func(t *Type) *Type { - return c.getTemplateLiteralType(texts, core.ReplaceElement(types, unionIndex, t)) + // Once a constituent produces an error (e.g. the cross product is too complex, or the template is too + // large), the whole result is the error type. Stop expanding the remaining constituents so we don't + // repeatedly redo the same work and report the same diagnostic for every combination. + failed := false + result := c.mapType(types[unionIndex], func(t *Type) *Type { + if failed { + return c.errorType + } + r := c.getTemplateLiteralType(texts, core.ReplaceElement(types, unionIndex, t)) + if r == c.errorType { + failed = true + } + return r }) + if failed { + return c.errorType + } + return result } if slices.Contains(types, c.wildcardType) { return c.wildcardType From 4595875f5051f336e1f2435fed2ea2247b96354b Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Chauhan Date: Wed, 4 Nov 2026 14:38:19 +0530 Subject: [PATCH 2/2] Add templateLiteralComplexityLimit compiler test Covers the TS2590 cross product limit used through a call and a nested template, and checks that a limit error for one alias does not leak into an unrelated alias resolved in the same context. --- .../templateLiteralComplexityLimit.errors.txt | 31 ++++++++ .../templateLiteralComplexityLimit.symbols | 72 +++++++++++++++++++ .../templateLiteralComplexityLimit.types | 57 +++++++++++++++ .../templateLiteralComplexityLimit.ts | 21 ++++++ 4 files changed, 181 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.types create mode 100644 tsc/testdata/tests/cases/compiler/templateLiteralComplexityLimit.ts diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.errors.txt b/tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.errors.txt new file mode 100644 index 0000000000000..7f47f0ed5869e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.errors.txt @@ -0,0 +1,31 @@ +templateLiteralComplexityLimit.ts(5,15): error TS2590: Expression produces a union type that is too complex to represent. +templateLiteralComplexityLimit.ts(13,14): error TS2590: Expression produces a union type that is too complex to represent. +templateLiteralComplexityLimit.ts(18,5): error TS2322: Type '123' is not assignable to type '"x" | "y"'. + + +==== templateLiteralComplexityLimit.ts (3 errors) ==== + // Once the cross product limit is reported, later expansions for the same node must bail out quickly. + type A = "a0" | "a1" | "a2" | "a3" | "a4" | "a5" | "a6" | "a7" | "a8" | "a9" | "a10" | "a11" | "a12" | "a13" | "a14" | "a15" | "a16" | "a17" | "a18" | "a19" | "a20" | "a21" | "a22" | "a23" | "a24" | "a25" | "a26" | "a27" | "a28" | "a29" | "a30" | "a31" | "a32" | "a33" | "a34" | "a35" | "a36" | "a37" | "a38" | "a39" | "a40" | "a41" | "a42" | "a43" | "a44" | "a45" | "a46" | "a47" | "a48" | "a49"; + type B = "b0" | "b1" | "b2" | "b3" | "b4" | "b5" | "b6" | "b7" | "b8" | "b9" | "b10" | "b11" | "b12" | "b13" | "b14" | "b15" | "b16" | "b17" | "b18" | "b19" | "b20" | "b21" | "b22" | "b23" | "b24" | "b25" | "b26" | "b27" | "b28" | "b29" | "b30" | "b31" | "b32" | "b33" | "b34" | "b35" | "b36" | "b37" | "b38" | "b39" | "b40" | "b41" | "b42" | "b43" | "b44" | "b45" | "b46" | "b47" | "b48" | "b49"; + type C = "c0" | "c1" | "c2" | "c3" | "c4" | "c5" | "c6" | "c7" | "c8" | "c9" | "c10" | "c11" | "c12" | "c13" | "c14" | "c15" | "c16" | "c17" | "c18" | "c19" | "c20" | "c21" | "c22" | "c23" | "c24" | "c25" | "c26" | "c27" | "c28" | "c29" | "c30" | "c31" | "c32" | "c33" | "c34" | "c35" | "c36" | "c37" | "c38" | "c39" | "c40" | "c41" | "c42" | "c43" | "c44" | "c45" | "c46" | "c47" | "c48" | "c49"; + type TooBig = `${A}.${B}.${C}`; + ~~~~~~~~~~~~~~~~ +!!! error TS2590: Expression produces a union type that is too complex to represent. + declare function f(x: T): T; + f("a0.b0.c0"); + type Nested = `${TooBig}.${A}`; + declare const n: Nested; + export const len = n.length; + + // A limit error for one alias must not leak into unrelated aliases resolved in the same context. + type Force = Combined; + ~~~~~~~~ +!!! error TS2590: Expression produces a union type that is too complex to represent. + type Combined = TooBig2 | Small; + type TooBig2 = `${A}.${B}.${C}`; + type Small = `${"x" | "y"}`; + declare const force: Force; + let small: Small = 123; + ~~~~~ +!!! error TS2322: Type '123' is not assignable to type '"x" | "y"'. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.symbols b/tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.symbols new file mode 100644 index 0000000000000..bc44bf3e7b4fa --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.symbols @@ -0,0 +1,72 @@ +//// [tests/cases/compiler/templateLiteralComplexityLimit.ts] //// + +=== templateLiteralComplexityLimit.ts === +// Once the cross product limit is reported, later expansions for the same node must bail out quickly. +type A = "a0" | "a1" | "a2" | "a3" | "a4" | "a5" | "a6" | "a7" | "a8" | "a9" | "a10" | "a11" | "a12" | "a13" | "a14" | "a15" | "a16" | "a17" | "a18" | "a19" | "a20" | "a21" | "a22" | "a23" | "a24" | "a25" | "a26" | "a27" | "a28" | "a29" | "a30" | "a31" | "a32" | "a33" | "a34" | "a35" | "a36" | "a37" | "a38" | "a39" | "a40" | "a41" | "a42" | "a43" | "a44" | "a45" | "a46" | "a47" | "a48" | "a49"; +>A : Symbol(A, Decl(templateLiteralComplexityLimit.ts, 0, 0)) + +type B = "b0" | "b1" | "b2" | "b3" | "b4" | "b5" | "b6" | "b7" | "b8" | "b9" | "b10" | "b11" | "b12" | "b13" | "b14" | "b15" | "b16" | "b17" | "b18" | "b19" | "b20" | "b21" | "b22" | "b23" | "b24" | "b25" | "b26" | "b27" | "b28" | "b29" | "b30" | "b31" | "b32" | "b33" | "b34" | "b35" | "b36" | "b37" | "b38" | "b39" | "b40" | "b41" | "b42" | "b43" | "b44" | "b45" | "b46" | "b47" | "b48" | "b49"; +>B : Symbol(B, Decl(templateLiteralComplexityLimit.ts, 1, 397)) + +type C = "c0" | "c1" | "c2" | "c3" | "c4" | "c5" | "c6" | "c7" | "c8" | "c9" | "c10" | "c11" | "c12" | "c13" | "c14" | "c15" | "c16" | "c17" | "c18" | "c19" | "c20" | "c21" | "c22" | "c23" | "c24" | "c25" | "c26" | "c27" | "c28" | "c29" | "c30" | "c31" | "c32" | "c33" | "c34" | "c35" | "c36" | "c37" | "c38" | "c39" | "c40" | "c41" | "c42" | "c43" | "c44" | "c45" | "c46" | "c47" | "c48" | "c49"; +>C : Symbol(C, Decl(templateLiteralComplexityLimit.ts, 2, 397)) + +type TooBig = `${A}.${B}.${C}`; +>TooBig : Symbol(TooBig, Decl(templateLiteralComplexityLimit.ts, 3, 397)) +>A : Symbol(A, Decl(templateLiteralComplexityLimit.ts, 0, 0)) +>B : Symbol(B, Decl(templateLiteralComplexityLimit.ts, 1, 397)) +>C : Symbol(C, Decl(templateLiteralComplexityLimit.ts, 2, 397)) + +declare function f(x: T): T; +>f : Symbol(f, Decl(templateLiteralComplexityLimit.ts, 4, 31)) +>T : Symbol(T, Decl(templateLiteralComplexityLimit.ts, 5, 19)) +>x : Symbol(x, Decl(templateLiteralComplexityLimit.ts, 5, 37)) +>T : Symbol(T, Decl(templateLiteralComplexityLimit.ts, 5, 19)) +>T : Symbol(T, Decl(templateLiteralComplexityLimit.ts, 5, 19)) + +f("a0.b0.c0"); +>f : Symbol(f, Decl(templateLiteralComplexityLimit.ts, 4, 31)) +>TooBig : Symbol(TooBig, Decl(templateLiteralComplexityLimit.ts, 3, 397)) + +type Nested = `${TooBig}.${A}`; +>Nested : Symbol(Nested, Decl(templateLiteralComplexityLimit.ts, 6, 22)) +>TooBig : Symbol(TooBig, Decl(templateLiteralComplexityLimit.ts, 3, 397)) +>A : Symbol(A, Decl(templateLiteralComplexityLimit.ts, 0, 0)) + +declare const n: Nested; +>n : Symbol(n, Decl(templateLiteralComplexityLimit.ts, 8, 13)) +>Nested : Symbol(Nested, Decl(templateLiteralComplexityLimit.ts, 6, 22)) + +export const len = n.length; +>len : Symbol(len, Decl(templateLiteralComplexityLimit.ts, 9, 12)) +>n.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>n : Symbol(n, Decl(templateLiteralComplexityLimit.ts, 8, 13)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + +// A limit error for one alias must not leak into unrelated aliases resolved in the same context. +type Force = Combined; +>Force : Symbol(Force, Decl(templateLiteralComplexityLimit.ts, 9, 28)) +>Combined : Symbol(Combined, Decl(templateLiteralComplexityLimit.ts, 12, 22)) + +type Combined = TooBig2 | Small; +>Combined : Symbol(Combined, Decl(templateLiteralComplexityLimit.ts, 12, 22)) +>TooBig2 : Symbol(TooBig2, Decl(templateLiteralComplexityLimit.ts, 13, 32)) +>Small : Symbol(Small, Decl(templateLiteralComplexityLimit.ts, 14, 32)) + +type TooBig2 = `${A}.${B}.${C}`; +>TooBig2 : Symbol(TooBig2, Decl(templateLiteralComplexityLimit.ts, 13, 32)) +>A : Symbol(A, Decl(templateLiteralComplexityLimit.ts, 0, 0)) +>B : Symbol(B, Decl(templateLiteralComplexityLimit.ts, 1, 397)) +>C : Symbol(C, Decl(templateLiteralComplexityLimit.ts, 2, 397)) + +type Small = `${"x" | "y"}`; +>Small : Symbol(Small, Decl(templateLiteralComplexityLimit.ts, 14, 32)) + +declare const force: Force; +>force : Symbol(force, Decl(templateLiteralComplexityLimit.ts, 16, 13)) +>Force : Symbol(Force, Decl(templateLiteralComplexityLimit.ts, 9, 28)) + +let small: Small = 123; +>small : Symbol(small, Decl(templateLiteralComplexityLimit.ts, 17, 3)) +>Small : Symbol(Small, Decl(templateLiteralComplexityLimit.ts, 14, 32)) + diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.types b/tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.types new file mode 100644 index 0000000000000..5745028bb2685 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.types @@ -0,0 +1,57 @@ +//// [tests/cases/compiler/templateLiteralComplexityLimit.ts] //// + +=== templateLiteralComplexityLimit.ts === +// Once the cross product limit is reported, later expansions for the same node must bail out quickly. +type A = "a0" | "a1" | "a2" | "a3" | "a4" | "a5" | "a6" | "a7" | "a8" | "a9" | "a10" | "a11" | "a12" | "a13" | "a14" | "a15" | "a16" | "a17" | "a18" | "a19" | "a20" | "a21" | "a22" | "a23" | "a24" | "a25" | "a26" | "a27" | "a28" | "a29" | "a30" | "a31" | "a32" | "a33" | "a34" | "a35" | "a36" | "a37" | "a38" | "a39" | "a40" | "a41" | "a42" | "a43" | "a44" | "a45" | "a46" | "a47" | "a48" | "a49"; +>A : A + +type B = "b0" | "b1" | "b2" | "b3" | "b4" | "b5" | "b6" | "b7" | "b8" | "b9" | "b10" | "b11" | "b12" | "b13" | "b14" | "b15" | "b16" | "b17" | "b18" | "b19" | "b20" | "b21" | "b22" | "b23" | "b24" | "b25" | "b26" | "b27" | "b28" | "b29" | "b30" | "b31" | "b32" | "b33" | "b34" | "b35" | "b36" | "b37" | "b38" | "b39" | "b40" | "b41" | "b42" | "b43" | "b44" | "b45" | "b46" | "b47" | "b48" | "b49"; +>B : B + +type C = "c0" | "c1" | "c2" | "c3" | "c4" | "c5" | "c6" | "c7" | "c8" | "c9" | "c10" | "c11" | "c12" | "c13" | "c14" | "c15" | "c16" | "c17" | "c18" | "c19" | "c20" | "c21" | "c22" | "c23" | "c24" | "c25" | "c26" | "c27" | "c28" | "c29" | "c30" | "c31" | "c32" | "c33" | "c34" | "c35" | "c36" | "c37" | "c38" | "c39" | "c40" | "c41" | "c42" | "c43" | "c44" | "c45" | "c46" | "c47" | "c48" | "c49"; +>C : C + +type TooBig = `${A}.${B}.${C}`; +>TooBig : any + +declare function f(x: T): T; +>f : (x: T) => T +>x : T + +f("a0.b0.c0"); +>f("a0.b0.c0") : any +>f : (x: T) => T +>"a0.b0.c0" : "a0.b0.c0" + +type Nested = `${TooBig}.${A}`; +>Nested : `${any}.a0` | `${any}.a1` | `${any}.a10` | `${any}.a11` | `${any}.a12` | `${any}.a13` | `${any}.a14` | `${any}.a15` | `${any}.a16` | `${any}.a17` | `${any}.a18` | `${any}.a19` | `${any}.a2` | `${any}.a20` | `${any}.a21` | `${any}.a22` | `${any}.a23` | `${any}.a24` | `${any}.a25` | `${any}.a26` | `${any}.a27` | `${any}.a28` | `${any}.a29` | `${any}.a3` | `${any}.a30` | `${any}.a31` | `${any}.a32` | `${any}.a33` | `${any}.a34` | `${any}.a35` | `${any}.a36` | `${any}.a37` | `${any}.a38` | `${any}.a39` | `${any}.a4` | `${any}.a40` | `${any}.a41` | `${any}.a42` | `${any}.a43` | `${any}.a44` | `${any}.a45` | `${any}.a46` | `${any}.a47` | `${any}.a48` | `${any}.a49` | `${any}.a5` | `${any}.a6` | `${any}.a7` | `${any}.a8` | `${any}.a9` + +declare const n: Nested; +>n : `${any}.a0` | `${any}.a1` | `${any}.a10` | `${any}.a11` | `${any}.a12` | `${any}.a13` | `${any}.a14` | `${any}.a15` | `${any}.a16` | `${any}.a17` | `${any}.a18` | `${any}.a19` | `${any}.a2` | `${any}.a20` | `${any}.a21` | `${any}.a22` | `${any}.a23` | `${any}.a24` | `${any}.a25` | `${any}.a26` | `${any}.a27` | `${any}.a28` | `${any}.a29` | `${any}.a3` | `${any}.a30` | `${any}.a31` | `${any}.a32` | `${any}.a33` | `${any}.a34` | `${any}.a35` | `${any}.a36` | `${any}.a37` | `${any}.a38` | `${any}.a39` | `${any}.a4` | `${any}.a40` | `${any}.a41` | `${any}.a42` | `${any}.a43` | `${any}.a44` | `${any}.a45` | `${any}.a46` | `${any}.a47` | `${any}.a48` | `${any}.a49` | `${any}.a5` | `${any}.a6` | `${any}.a7` | `${any}.a8` | `${any}.a9` + +export const len = n.length; +>len : number +>n.length : number +>n : `${any}.a0` | `${any}.a1` | `${any}.a10` | `${any}.a11` | `${any}.a12` | `${any}.a13` | `${any}.a14` | `${any}.a15` | `${any}.a16` | `${any}.a17` | `${any}.a18` | `${any}.a19` | `${any}.a2` | `${any}.a20` | `${any}.a21` | `${any}.a22` | `${any}.a23` | `${any}.a24` | `${any}.a25` | `${any}.a26` | `${any}.a27` | `${any}.a28` | `${any}.a29` | `${any}.a3` | `${any}.a30` | `${any}.a31` | `${any}.a32` | `${any}.a33` | `${any}.a34` | `${any}.a35` | `${any}.a36` | `${any}.a37` | `${any}.a38` | `${any}.a39` | `${any}.a4` | `${any}.a40` | `${any}.a41` | `${any}.a42` | `${any}.a43` | `${any}.a44` | `${any}.a45` | `${any}.a46` | `${any}.a47` | `${any}.a48` | `${any}.a49` | `${any}.a5` | `${any}.a6` | `${any}.a7` | `${any}.a8` | `${any}.a9` +>length : number + +// A limit error for one alias must not leak into unrelated aliases resolved in the same context. +type Force = Combined; +>Force : any + +type Combined = TooBig2 | Small; +>Combined : any + +type TooBig2 = `${A}.${B}.${C}`; +>TooBig2 : any + +type Small = `${"x" | "y"}`; +>Small : "x" | "y" + +declare const force: Force; +>force : any + +let small: Small = 123; +>small : "x" | "y" +>123 : 123 + diff --git a/tsc/testdata/tests/cases/compiler/templateLiteralComplexityLimit.ts b/tsc/testdata/tests/cases/compiler/templateLiteralComplexityLimit.ts new file mode 100644 index 0000000000000..1e77dcadcc296 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/templateLiteralComplexityLimit.ts @@ -0,0 +1,21 @@ +// @strict: true +// @noEmit: true + +// Once the cross product limit is reported, later expansions for the same node must bail out quickly. +type A = "a0" | "a1" | "a2" | "a3" | "a4" | "a5" | "a6" | "a7" | "a8" | "a9" | "a10" | "a11" | "a12" | "a13" | "a14" | "a15" | "a16" | "a17" | "a18" | "a19" | "a20" | "a21" | "a22" | "a23" | "a24" | "a25" | "a26" | "a27" | "a28" | "a29" | "a30" | "a31" | "a32" | "a33" | "a34" | "a35" | "a36" | "a37" | "a38" | "a39" | "a40" | "a41" | "a42" | "a43" | "a44" | "a45" | "a46" | "a47" | "a48" | "a49"; +type B = "b0" | "b1" | "b2" | "b3" | "b4" | "b5" | "b6" | "b7" | "b8" | "b9" | "b10" | "b11" | "b12" | "b13" | "b14" | "b15" | "b16" | "b17" | "b18" | "b19" | "b20" | "b21" | "b22" | "b23" | "b24" | "b25" | "b26" | "b27" | "b28" | "b29" | "b30" | "b31" | "b32" | "b33" | "b34" | "b35" | "b36" | "b37" | "b38" | "b39" | "b40" | "b41" | "b42" | "b43" | "b44" | "b45" | "b46" | "b47" | "b48" | "b49"; +type C = "c0" | "c1" | "c2" | "c3" | "c4" | "c5" | "c6" | "c7" | "c8" | "c9" | "c10" | "c11" | "c12" | "c13" | "c14" | "c15" | "c16" | "c17" | "c18" | "c19" | "c20" | "c21" | "c22" | "c23" | "c24" | "c25" | "c26" | "c27" | "c28" | "c29" | "c30" | "c31" | "c32" | "c33" | "c34" | "c35" | "c36" | "c37" | "c38" | "c39" | "c40" | "c41" | "c42" | "c43" | "c44" | "c45" | "c46" | "c47" | "c48" | "c49"; +type TooBig = `${A}.${B}.${C}`; +declare function f(x: T): T; +f("a0.b0.c0"); +type Nested = `${TooBig}.${A}`; +declare const n: Nested; +export const len = n.length; + +// A limit error for one alias must not leak into unrelated aliases resolved in the same context. +type Force = Combined; +type Combined = TooBig2 | Small; +type TooBig2 = `${A}.${B}.${C}`; +type Small = `${"x" | "y"}`; +declare const force: Force; +let small: Small = 123;