Skip to content

Commit a53f871

Browse files
committed
Key template literal bailout off the instantiation count, not the current node
Address review feedback: a node-scoped sticky bailout after a cross product error could cache errorType for unrelated aliases resolved under the same node. Only bail out of union expansion once the 5M instantiation limit has been exceeded, which already makes instantiateType yield errorType for the rest of the statement. Add a regression case for the alias scenario.
1 parent 1067def commit a53f871

5 files changed

Lines changed: 78 additions & 8 deletions

File tree

‎tsc/internal/checker/checker.go‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,6 @@ type Checker struct {
605605
SignatureCount uint32
606606
TotalInstantiationCount uint32
607607
instantiationCount uint32
608-
complexityLimitNode *ast.Node // node for which a type complexity limit error was already reported
609608
instantiationLimitReportedNode *ast.Node // node for which the instantiation count limit error was already reported
610609
memberLayoutCount uint32
611610
instantiationStack []*Type
@@ -22762,6 +22761,9 @@ func (c *Checker) symbolIsValueEx(symbol *ast.Symbol, includeTypeOnlyMembers boo
2276222761
c.getSymbolFlagsEx(symbol, !includeTypeOnlyMembers, false /*excludeLocalMeanings*/)&ast.SymbolFlagsValue != 0
2276322762
}
2276422763

22764+
// The maximum number of type instantiations performed for a single statement or expression.
22765+
const maxInstantiationCount = 5_000_000
22766+
2276522767
func (c *Checker) instantiateType(t *Type, m *TypeMapper) *Type {
2276622768
return c.instantiateTypeWithAlias(t, m, nil /*alias*/)
2276722769
}
@@ -22773,15 +22775,14 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl
2277322775
if t == nil || m == nil || !(c.couldContainTypeVariables(t) || (t.alias != nil && len(t.alias.typeArguments) > 0 && core.Some(t.alias.typeArguments, c.couldContainTypeVariables))) {
2277422776
return t
2277522777
}
22776-
if len(c.instantiationStack) == 100 || c.instantiationCount >= 5_000_000 {
22778+
if len(c.instantiationStack) == 100 || c.instantiationCount >= maxInstantiationCount {
2277722779
// We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement
2277822780
// or expression. There is a very high likelihood we're dealing with a combination of infinite generic types
2277922781
// that perpetually generate new type identities, so we stop the recursion here by yielding the error type.
2278022782
if tr := c.tracer; tr != nil {
2278122783
tr.Instant(tracing.PhaseCheckTypes, "instantiateType_DepthLimit", map[string]any{"typeId": t.id, "instantiationDepth": len(c.instantiationStack), "instantiationCount": c.instantiationCount})
2278222784
}
22783-
if c.instantiationCount >= 5_000_000 {
22784-
c.complexityLimitNode = c.currentNode
22785+
if c.instantiationCount >= maxInstantiationCount {
2278522786
// Once the count limit is exceeded every further instantiation for this node hits the limit again.
2278622787
// Report the error only once per node instead of allocating millions of identical diagnostics.
2278722788
if c.instantiationLimitReportedNode == c.currentNode && c.currentNode != nil {
@@ -27442,7 +27443,6 @@ func (c *Checker) checkCrossProductUnion(types []*Type) bool {
2744227443
tr.Instant(tracing.PhaseCheckTypes, "checkCrossProductUnion_DepthLimit", map[string]any{"size": size})
2744327444
}
2744427445
c.error(c.currentNode, diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent)
27445-
c.complexityLimitNode = c.currentNode
2744627446
return false
2744727447
}
2744827448
return true
@@ -29965,8 +29965,9 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type {
2996529965
return t.flags&(TypeFlagsNever|TypeFlagsUnion) != 0
2996629966
})
2996729967
if unionIndex >= 0 {
29968-
if c.complexityLimitNode != nil && c.complexityLimitNode == c.currentNode {
29969-
// A complexity error was already reported for this node. Expanding further cross products
29968+
if c.instantiationCount >= maxInstantiationCount {
29969+
// The instantiation count limit was already exceeded for the current statement or expression, so
29970+
// instantiateType yields the error type for everything from here on. Expanding further cross products
2997029971
// would only allocate huge unions of string literals that can never be used.
2997129972
return c.errorType
2997229973
}

‎tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.errors.txt‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
templateLiteralComplexityLimit.ts(5,15): error TS2590: Expression produces a union type that is too complex to represent.
2+
templateLiteralComplexityLimit.ts(13,14): error TS2590: Expression produces a union type that is too complex to represent.
3+
templateLiteralComplexityLimit.ts(18,5): error TS2322: Type '123' is not assignable to type '"x" | "y"'.
24

35

4-
==== templateLiteralComplexityLimit.ts (1 errors) ====
6+
==== templateLiteralComplexityLimit.ts (3 errors) ====
57
// Once the cross product limit is reported, later expansions for the same node must bail out quickly.
68
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";
79
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";
@@ -14,4 +16,16 @@ templateLiteralComplexityLimit.ts(5,15): error TS2590: Expression produces a uni
1416
type Nested = `${TooBig}.${A}`;
1517
declare const n: Nested;
1618
export const len = n.length;
19+
20+
// A limit error for one alias must not leak into unrelated aliases resolved in the same context.
21+
type Force = Combined;
22+
~~~~~~~~
23+
!!! error TS2590: Expression produces a union type that is too complex to represent.
24+
type Combined = TooBig2 | Small;
25+
type TooBig2 = `${A}.${B}.${C}`;
26+
type Small = `${"x" | "y"}`;
27+
declare const force: Force;
28+
let small: Small = 123;
29+
~~~~~
30+
!!! error TS2322: Type '123' is not assignable to type '"x" | "y"'.
1731

‎tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.symbols‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,30 @@ export const len = n.length;
4343
>n : Symbol(n, Decl(templateLiteralComplexityLimit.ts, 8, 13))
4444
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
4545

46+
// A limit error for one alias must not leak into unrelated aliases resolved in the same context.
47+
type Force = Combined;
48+
>Force : Symbol(Force, Decl(templateLiteralComplexityLimit.ts, 9, 28))
49+
>Combined : Symbol(Combined, Decl(templateLiteralComplexityLimit.ts, 12, 22))
50+
51+
type Combined = TooBig2 | Small;
52+
>Combined : Symbol(Combined, Decl(templateLiteralComplexityLimit.ts, 12, 22))
53+
>TooBig2 : Symbol(TooBig2, Decl(templateLiteralComplexityLimit.ts, 13, 32))
54+
>Small : Symbol(Small, Decl(templateLiteralComplexityLimit.ts, 14, 32))
55+
56+
type TooBig2 = `${A}.${B}.${C}`;
57+
>TooBig2 : Symbol(TooBig2, Decl(templateLiteralComplexityLimit.ts, 13, 32))
58+
>A : Symbol(A, Decl(templateLiteralComplexityLimit.ts, 0, 0))
59+
>B : Symbol(B, Decl(templateLiteralComplexityLimit.ts, 1, 397))
60+
>C : Symbol(C, Decl(templateLiteralComplexityLimit.ts, 2, 397))
61+
62+
type Small = `${"x" | "y"}`;
63+
>Small : Symbol(Small, Decl(templateLiteralComplexityLimit.ts, 14, 32))
64+
65+
declare const force: Force;
66+
>force : Symbol(force, Decl(templateLiteralComplexityLimit.ts, 16, 13))
67+
>Force : Symbol(Force, Decl(templateLiteralComplexityLimit.ts, 9, 28))
68+
69+
let small: Small = 123;
70+
>small : Symbol(small, Decl(templateLiteralComplexityLimit.ts, 17, 3))
71+
>Small : Symbol(Small, Decl(templateLiteralComplexityLimit.ts, 14, 32))
72+

‎tsc/testdata/baselines/reference/compiler/templateLiteralComplexityLimit.types‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,23 @@ export const len = n.length;
3535
>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`
3636
>length : number
3737

38+
// A limit error for one alias must not leak into unrelated aliases resolved in the same context.
39+
type Force = Combined;
40+
>Force : any
41+
42+
type Combined = TooBig2 | Small;
43+
>Combined : any
44+
45+
type TooBig2 = `${A}.${B}.${C}`;
46+
>TooBig2 : any
47+
48+
type Small = `${"x" | "y"}`;
49+
>Small : "x" | "y"
50+
51+
declare const force: Force;
52+
>force : any
53+
54+
let small: Small = 123;
55+
>small : "x" | "y"
56+
>123 : 123
57+

‎tsc/testdata/tests/cases/compiler/templateLiteralComplexityLimit.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@ f<TooBig>("a0.b0.c0");
1111
type Nested = `${TooBig}.${A}`;
1212
declare const n: Nested;
1313
export const len = n.length;
14+
15+
// A limit error for one alias must not leak into unrelated aliases resolved in the same context.
16+
type Force = Combined;
17+
type Combined = TooBig2 | Small;
18+
type TooBig2 = `${A}.${B}.${C}`;
19+
type Small = `${"x" | "y"}`;
20+
declare const force: Force;
21+
let small: Small = 123;

0 commit comments

Comments
 (0)