Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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*/)
}
Expand All @@ -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:
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T extends string>(x: T): T;
f<TooBig>("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"'.

Original file line number Diff line number Diff line change
@@ -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<T extends string>(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<TooBig>("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))

Original file line number Diff line number Diff line change
@@ -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<T extends string>(x: T): T;
>f : <T extends string>(x: T) => T
>x : T

f<TooBig>("a0.b0.c0");
>f<TooBig>("a0.b0.c0") : any
>f : <T extends string>(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

Original file line number Diff line number Diff line change
@@ -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<T extends string>(x: T): T;
f<TooBig>("a0.b0.c0");
type Nested = `${TooBig}.${A}`;
Comment on lines +8 to +11
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;