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 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;