Skip to content

Commit b9eeb74

Browse files
authored
Tail recursive evaluation of conditional types (#45711)
* Implement tail-recursion for conditional types and lower general instantiation depth limit to 100 * Add tests * Skip caching for tail recursive temporary conditional types
1 parent 4f5bbd0 commit b9eeb74

File tree

5 files changed

+296
-4
lines changed

5 files changed

+296
-4
lines changed

Diff for: src/compiler/checker.ts

+46-4
Original file line numberDiff line numberDiff line change
@@ -15265,10 +15265,18 @@ namespace ts {
1526515265
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
1526615266
let result;
1526715267
let extraTypes: Type[] | undefined;
15268+
let tailCount = 0;
1526815269
// We loop here for an immediately nested conditional type in the false position, effectively treating
1526915270
// types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for
15270-
// purposes of resolution. This means such types aren't subject to the instantiation depth limiter.
15271+
// purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of
15272+
// another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive
15273+
// cases we increment the tail recursion counter and stop after 1000 iterations.
1527115274
while (true) {
15275+
if (tailCount === 1000) {
15276+
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
15277+
result = errorType;
15278+
break;
15279+
}
1527215280
const isUnwrapped = isTypicalNondistributiveConditional(root);
1527315281
const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper);
1527415282
const checkTypeInstantiable = isGenericType(checkType);
@@ -15312,6 +15320,9 @@ namespace ts {
1531215320
root = newRoot;
1531315321
continue;
1531415322
}
15323+
if (canTailRecurse(falseType, mapper)) {
15324+
continue;
15325+
}
1531515326
}
1531615327
result = instantiateType(falseType, mapper);
1531715328
break;
@@ -15322,7 +15333,12 @@ namespace ts {
1532215333
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
1532315334
// doesn't immediately resolve to 'string' instead of being deferred.
1532415335
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
15325-
result = instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper);
15336+
const trueType = getTypeFromTypeNode(root.node.trueType);
15337+
const trueMapper = combinedMapper || mapper;
15338+
if (canTailRecurse(trueType, trueMapper)) {
15339+
continue;
15340+
}
15341+
result = instantiateType(trueType, trueMapper);
1532615342
break;
1532715343
}
1532815344
}
@@ -15338,6 +15354,32 @@ namespace ts {
1533815354
break;
1533915355
}
1534015356
return extraTypes ? getUnionType(append(extraTypes, result)) : result;
15357+
// We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and
15358+
// (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check
15359+
// type. Note that recursion is possible only through aliased conditional types, so we only increment the tail
15360+
// recursion counter for those.
15361+
function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) {
15362+
if (newType.flags & TypeFlags.Conditional && newMapper) {
15363+
const newRoot = (newType as ConditionalType).root;
15364+
if (newRoot.outerTypeParameters) {
15365+
const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper);
15366+
const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper));
15367+
const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments);
15368+
const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined;
15369+
if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) {
15370+
root = newRoot;
15371+
mapper = newRootMapper;
15372+
aliasSymbol = undefined;
15373+
aliasTypeArguments = undefined;
15374+
if (newRoot.aliasSymbol) {
15375+
tailCount++;
15376+
}
15377+
return true;
15378+
}
15379+
}
15380+
}
15381+
return false;
15382+
}
1534115383
}
1534215384

1534315385
function getTrueTypeFromConditionalType(type: ConditionalType) {
@@ -16358,8 +16400,8 @@ namespace ts {
1635816400
if (!couldContainTypeVariables(type)) {
1635916401
return type;
1636016402
}
16361-
if (instantiationDepth === 500 || instantiationCount >= 5000000) {
16362-
// We have reached 500 recursive type instantiations, or 5M type instantiations caused by the same statement
16403+
if (instantiationDepth === 100 || instantiationCount >= 5000000) {
16404+
// We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement
1636316405
// or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types
1636416406
// that perpetually generate new type identities, so we stop the recursion here by yielding the error type.
1636516407
tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//// [tailRecursiveConditionalTypes.ts]
2+
type Trim<S extends string> =
3+
S extends ` ${infer T}` ? Trim<T> :
4+
S extends `${infer T} ` ? Trim<T> :
5+
S;
6+
7+
type T10 = Trim<' hello '>;
8+
type T11 = Trim<' hello '>;
9+
10+
type GetChars<S> = GetCharsRec<S, never>;
11+
type GetCharsRec<S, Acc> =
12+
S extends `${infer Char}${infer Rest}` ? GetCharsRec<Rest, Char | Acc> : Acc;
13+
14+
type T20 = GetChars<'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'>;
15+
16+
type Reverse<T> = any[] extends T ? T : ReverseRec<T, []>;
17+
type ReverseRec<T, Acc extends unknown[]> =
18+
T extends [infer Head, ...infer Tail] ? ReverseRec<Tail, [Head, ...Acc]> : Acc;
19+
20+
type T30 = Reverse<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>;
21+
type T31 = Reverse<string[]>;
22+
23+
type TupleOf<T, N extends number> = number extends N ? T[] : TupleOfRec<T, N, []>;
24+
type TupleOfRec<T, N extends number, Acc extends unknown[]> =
25+
Acc["length"] extends N ? Acc : TupleOfRec<T, N, [T, ...Acc]>;
26+
27+
type T40 = TupleOf<any, 200>;
28+
type T41 = TupleOf<any, number>;
29+
30+
31+
//// [tailRecursiveConditionalTypes.js]
32+
"use strict";
33+
34+
35+
//// [tailRecursiveConditionalTypes.d.ts]
36+
declare type Trim<S extends string> = S extends ` ${infer T}` ? Trim<T> : S extends `${infer T} ` ? Trim<T> : S;
37+
declare type T10 = Trim<' hello '>;
38+
declare type T11 = Trim<' hello '>;
39+
declare type GetChars<S> = GetCharsRec<S, never>;
40+
declare type GetCharsRec<S, Acc> = S extends `${infer Char}${infer Rest}` ? GetCharsRec<Rest, Char | Acc> : Acc;
41+
declare type T20 = GetChars<'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'>;
42+
declare type Reverse<T> = any[] extends T ? T : ReverseRec<T, []>;
43+
declare type ReverseRec<T, Acc extends unknown[]> = T extends [infer Head, ...infer Tail] ? ReverseRec<Tail, [Head, ...Acc]> : Acc;
44+
declare type T30 = Reverse<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>;
45+
declare type T31 = Reverse<string[]>;
46+
declare type TupleOf<T, N extends number> = number extends N ? T[] : TupleOfRec<T, N, []>;
47+
declare type TupleOfRec<T, N extends number, Acc extends unknown[]> = Acc["length"] extends N ? Acc : TupleOfRec<T, N, [T, ...Acc]>;
48+
declare type T40 = TupleOf<any, 200>;
49+
declare type T41 = TupleOf<any, number>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
=== tests/cases/compiler/tailRecursiveConditionalTypes.ts ===
2+
type Trim<S extends string> =
3+
>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0))
4+
>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 0, 10))
5+
6+
S extends ` ${infer T}` ? Trim<T> :
7+
>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 0, 10))
8+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 1, 23))
9+
>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0))
10+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 1, 23))
11+
12+
S extends `${infer T} ` ? Trim<T> :
13+
>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 0, 10))
14+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 2, 22))
15+
>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0))
16+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 2, 22))
17+
18+
S;
19+
>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 0, 10))
20+
21+
type T10 = Trim<' hello '>;
22+
>T10 : Symbol(T10, Decl(tailRecursiveConditionalTypes.ts, 3, 6))
23+
>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0))
24+
25+
type T11 = Trim<' hello '>;
26+
>T11 : Symbol(T11, Decl(tailRecursiveConditionalTypes.ts, 5, 170))
27+
>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0))
28+
29+
type GetChars<S> = GetCharsRec<S, never>;
30+
>GetChars : Symbol(GetChars, Decl(tailRecursiveConditionalTypes.ts, 6, 170))
31+
>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 8, 14))
32+
>GetCharsRec : Symbol(GetCharsRec, Decl(tailRecursiveConditionalTypes.ts, 8, 41))
33+
>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 8, 14))
34+
35+
type GetCharsRec<S, Acc> =
36+
>GetCharsRec : Symbol(GetCharsRec, Decl(tailRecursiveConditionalTypes.ts, 8, 41))
37+
>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 9, 17))
38+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 9, 19))
39+
40+
S extends `${infer Char}${infer Rest}` ? GetCharsRec<Rest, Char | Acc> : Acc;
41+
>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 9, 17))
42+
>Char : Symbol(Char, Decl(tailRecursiveConditionalTypes.ts, 10, 22))
43+
>Rest : Symbol(Rest, Decl(tailRecursiveConditionalTypes.ts, 10, 35))
44+
>GetCharsRec : Symbol(GetCharsRec, Decl(tailRecursiveConditionalTypes.ts, 8, 41))
45+
>Rest : Symbol(Rest, Decl(tailRecursiveConditionalTypes.ts, 10, 35))
46+
>Char : Symbol(Char, Decl(tailRecursiveConditionalTypes.ts, 10, 22))
47+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 9, 19))
48+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 9, 19))
49+
50+
type T20 = GetChars<'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'>;
51+
>T20 : Symbol(T20, Decl(tailRecursiveConditionalTypes.ts, 10, 81))
52+
>GetChars : Symbol(GetChars, Decl(tailRecursiveConditionalTypes.ts, 6, 170))
53+
54+
type Reverse<T> = any[] extends T ? T : ReverseRec<T, []>;
55+
>Reverse : Symbol(Reverse, Decl(tailRecursiveConditionalTypes.ts, 12, 86))
56+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 14, 13))
57+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 14, 13))
58+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 14, 13))
59+
>ReverseRec : Symbol(ReverseRec, Decl(tailRecursiveConditionalTypes.ts, 14, 58))
60+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 14, 13))
61+
62+
type ReverseRec<T, Acc extends unknown[]> =
63+
>ReverseRec : Symbol(ReverseRec, Decl(tailRecursiveConditionalTypes.ts, 14, 58))
64+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 15, 16))
65+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 15, 18))
66+
67+
T extends [infer Head, ...infer Tail] ? ReverseRec<Tail, [Head, ...Acc]> : Acc;
68+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 15, 16))
69+
>Head : Symbol(Head, Decl(tailRecursiveConditionalTypes.ts, 16, 20))
70+
>Tail : Symbol(Tail, Decl(tailRecursiveConditionalTypes.ts, 16, 35))
71+
>ReverseRec : Symbol(ReverseRec, Decl(tailRecursiveConditionalTypes.ts, 14, 58))
72+
>Tail : Symbol(Tail, Decl(tailRecursiveConditionalTypes.ts, 16, 35))
73+
>Head : Symbol(Head, Decl(tailRecursiveConditionalTypes.ts, 16, 20))
74+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 15, 18))
75+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 15, 18))
76+
77+
type T30 = Reverse<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>;
78+
>T30 : Symbol(T30, Decl(tailRecursiveConditionalTypes.ts, 16, 83))
79+
>Reverse : Symbol(Reverse, Decl(tailRecursiveConditionalTypes.ts, 12, 86))
80+
81+
type T31 = Reverse<string[]>;
82+
>T31 : Symbol(T31, Decl(tailRecursiveConditionalTypes.ts, 18, 171))
83+
>Reverse : Symbol(Reverse, Decl(tailRecursiveConditionalTypes.ts, 12, 86))
84+
85+
type TupleOf<T, N extends number> = number extends N ? T[] : TupleOfRec<T, N, []>;
86+
>TupleOf : Symbol(TupleOf, Decl(tailRecursiveConditionalTypes.ts, 19, 29))
87+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 21, 13))
88+
>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 21, 15))
89+
>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 21, 15))
90+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 21, 13))
91+
>TupleOfRec : Symbol(TupleOfRec, Decl(tailRecursiveConditionalTypes.ts, 21, 82))
92+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 21, 13))
93+
>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 21, 15))
94+
95+
type TupleOfRec<T, N extends number, Acc extends unknown[]> =
96+
>TupleOfRec : Symbol(TupleOfRec, Decl(tailRecursiveConditionalTypes.ts, 21, 82))
97+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 22, 16))
98+
>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 22, 18))
99+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 22, 36))
100+
101+
Acc["length"] extends N ? Acc : TupleOfRec<T, N, [T, ...Acc]>;
102+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 22, 36))
103+
>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 22, 18))
104+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 22, 36))
105+
>TupleOfRec : Symbol(TupleOfRec, Decl(tailRecursiveConditionalTypes.ts, 21, 82))
106+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 22, 16))
107+
>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 22, 18))
108+
>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 22, 16))
109+
>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 22, 36))
110+
111+
type T40 = TupleOf<any, 200>;
112+
>T40 : Symbol(T40, Decl(tailRecursiveConditionalTypes.ts, 23, 66))
113+
>TupleOf : Symbol(TupleOf, Decl(tailRecursiveConditionalTypes.ts, 19, 29))
114+
115+
type T41 = TupleOf<any, number>;
116+
>T41 : Symbol(T41, Decl(tailRecursiveConditionalTypes.ts, 25, 29))
117+
>TupleOf : Symbol(TupleOf, Decl(tailRecursiveConditionalTypes.ts, 19, 29))
118+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
=== tests/cases/compiler/tailRecursiveConditionalTypes.ts ===
2+
type Trim<S extends string> =
3+
>Trim : Trim<S>
4+
5+
S extends ` ${infer T}` ? Trim<T> :
6+
S extends `${infer T} ` ? Trim<T> :
7+
S;
8+
9+
type T10 = Trim<' hello '>;
10+
>T10 : "hello"
11+
12+
type T11 = Trim<' hello '>;
13+
>T11 : "hello"
14+
15+
type GetChars<S> = GetCharsRec<S, never>;
16+
>GetChars : GetChars<S>
17+
18+
type GetCharsRec<S, Acc> =
19+
>GetCharsRec : GetCharsRec<S, Acc>
20+
21+
S extends `${infer Char}${infer Rest}` ? GetCharsRec<Rest, Char | Acc> : Acc;
22+
23+
type T20 = GetChars<'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'>;
24+
>T20 : "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
25+
26+
type Reverse<T> = any[] extends T ? T : ReverseRec<T, []>;
27+
>Reverse : Reverse<T>
28+
29+
type ReverseRec<T, Acc extends unknown[]> =
30+
>ReverseRec : ReverseRec<T, Acc>
31+
32+
T extends [infer Head, ...infer Tail] ? ReverseRec<Tail, [Head, ...Acc]> : Acc;
33+
34+
type T30 = Reverse<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>;
35+
>T30 : [9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
36+
37+
type T31 = Reverse<string[]>;
38+
>T31 : string[]
39+
40+
type TupleOf<T, N extends number> = number extends N ? T[] : TupleOfRec<T, N, []>;
41+
>TupleOf : TupleOf<T, N>
42+
43+
type TupleOfRec<T, N extends number, Acc extends unknown[]> =
44+
>TupleOfRec : TupleOfRec<T, N, Acc>
45+
46+
Acc["length"] extends N ? Acc : TupleOfRec<T, N, [T, ...Acc]>;
47+
48+
type T40 = TupleOf<any, 200>;
49+
>T40 : [any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any]
50+
51+
type T41 = TupleOf<any, number>;
52+
>T41 : any[]
53+

0 commit comments

Comments
 (0)