Skip to content

Commit cd30534

Browse files
authored
Recursive conditional types (#40002)
* Support recursive conditional types * Accept new API baselines * Accept new baselines * Simplify recursive type tracking in type inference * Accept new baselines * Add tests * Accept new baselines * Revise recursion tracking in type inference * Revise tests * Accept new baselines * Add more tests * Accept new baselines
1 parent 2426eb4 commit cd30534

11 files changed

+1392
-116
lines changed

src/compiler/checker.ts

+78-107
Large diffs are not rendered by default.

src/compiler/types.ts

-2
Original file line numberDiff line numberDiff line change
@@ -5290,8 +5290,6 @@ namespace ts {
52905290
node: ConditionalTypeNode;
52915291
checkType: Type;
52925292
extendsType: Type;
5293-
trueType: Type;
5294-
falseType: Type;
52955293
isDistributive: boolean;
52965294
inferTypeParameters?: TypeParameter[];
52975295
outerTypeParameters?: TypeParameter[];

tests/baselines/reference/api/tsserverlibrary.d.ts

-2
Original file line numberDiff line numberDiff line change
@@ -2605,8 +2605,6 @@ declare namespace ts {
26052605
node: ConditionalTypeNode;
26062606
checkType: Type;
26072607
extendsType: Type;
2608-
trueType: Type;
2609-
falseType: Type;
26102608
isDistributive: boolean;
26112609
inferTypeParameters?: TypeParameter[];
26122610
outerTypeParameters?: TypeParameter[];

tests/baselines/reference/api/typescript.d.ts

-2
Original file line numberDiff line numberDiff line change
@@ -2605,8 +2605,6 @@ declare namespace ts {
26052605
node: ConditionalTypeNode;
26062606
checkType: Type;
26072607
extendsType: Type;
2608-
trueType: Type;
2609-
falseType: Type;
26102608
isDistributive: boolean;
26112609
inferTypeParameters?: TypeParameter[];
26122610
outerTypeParameters?: TypeParameter[];

tests/baselines/reference/deeplyNestedConditionalTypes.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,5 @@ type T0 = Foo<99>;
108108
>T0 : "99"
109109

110110
type T1 = Foo<any>;
111-
>T1 : "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" | "20" | "21" | "22" | "23" | "24" | "25" | "26" | "27" | "28" | "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36" | "37" | "38" | "39" | "40" | "41" | "42" | "43" | "44" | "45" | "46" | "47" | "48" | "49" | "50" | "51" | "52" | "53" | "54" | "55" | "56" | "57" | "58" | "59" | "60" | "61" | "62" | "63" | "64" | "65" | "66" | "67" | "68" | "69" | "70" | "71" | "72" | "73" | "74" | "75" | "76" | "77" | "78" | "79" | "80" | "81" | "82" | "83" | "84" | "85" | "86" | "87" | "88" | "89" | "90" | "91" | "92" | "93" | "94" | "95" | "96" | "97" | "98" | "99"
111+
>T1 : "99" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" | "20" | "21" | "22" | "23" | "24" | "25" | "26" | "27" | "28" | "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36" | "37" | "38" | "39" | "40" | "41" | "42" | "43" | "44" | "45" | "46" | "47" | "48" | "49" | "50" | "51" | "52" | "53" | "54" | "55" | "56" | "57" | "58" | "59" | "60" | "61" | "62" | "63" | "64" | "65" | "66" | "67" | "68" | "69" | "70" | "71" | "72" | "73" | "74" | "75" | "76" | "77" | "78" | "79" | "80" | "81" | "82" | "83" | "84" | "85" | "86" | "87" | "88" | "89" | "90" | "91" | "92" | "93" | "94" | "95" | "96" | "97" | "98"
112112

tests/baselines/reference/promiseTypeInference.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ declare function convert(s: string): IPromise<number>;
2222
>s : string
2323

2424
var $$x = load("something").then(s => convert(s));
25-
>$$x : CPromise<unknown>
26-
>load("something").then(s => convert(s)) : CPromise<unknown>
25+
>$$x : CPromise<number>
26+
>load("something").then(s => convert(s)) : CPromise<number>
2727
>load("something").then : <U>(success?: (value: string) => CPromise<U>) => CPromise<U>
2828
>load("something") : CPromise<string>
2929
>load : (name: string) => CPromise<string>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
tests/cases/compiler/recursiveConditionalTypes.ts(16,11): error TS2589: Type instantiation is excessively deep and possibly infinite.
2+
tests/cases/compiler/recursiveConditionalTypes.ts(20,5): error TS2322: Type 'Awaited<T>' is not assignable to type 'Awaited<U>'.
3+
Type 'T' is not assignable to type 'U'.
4+
'U' could be instantiated with an arbitrary type which could be unrelated to 'T'.
5+
tests/cases/compiler/recursiveConditionalTypes.ts(21,5): error TS2322: Type 'T' is not assignable to type 'Awaited<T>'.
6+
tests/cases/compiler/recursiveConditionalTypes.ts(22,5): error TS2322: Type 'Awaited<T>' is not assignable to type 'T'.
7+
'T' could be instantiated with an arbitrary type which could be unrelated to 'Awaited<T>'.
8+
Type 'T | (T extends PromiseLike<infer U> ? Awaited<U> : T)' is not assignable to type 'T'.
9+
'T' could be instantiated with an arbitrary type which could be unrelated to 'T | (T extends PromiseLike<infer U> ? Awaited<U> : T)'.
10+
Type 'T extends PromiseLike<infer U> ? Awaited<U> : T' is not assignable to type 'T'.
11+
'T' could be instantiated with an arbitrary type which could be unrelated to 'T extends PromiseLike<infer U> ? Awaited<U> : T'.
12+
Type 'unknown' is not assignable to type 'T'.
13+
'T' could be instantiated with an arbitrary type which could be unrelated to 'unknown'.
14+
tests/cases/compiler/recursiveConditionalTypes.ts(35,11): error TS2589: Type instantiation is excessively deep and possibly infinite.
15+
tests/cases/compiler/recursiveConditionalTypes.ts(46,12): error TS2589: Type instantiation is excessively deep and possibly infinite.
16+
tests/cases/compiler/recursiveConditionalTypes.ts(49,5): error TS2322: Type 'TupleOf<number, M>' is not assignable to type 'TupleOf<number, N>'.
17+
Type 'number extends M ? number[] : _TupleOf<number, M, []>' is not assignable to type 'TupleOf<number, N>'.
18+
Type 'number[] | _TupleOf<number, M, []>' is not assignable to type 'TupleOf<number, N>'.
19+
Type 'number[]' is not assignable to type 'TupleOf<number, N>'.
20+
tests/cases/compiler/recursiveConditionalTypes.ts(50,5): error TS2322: Type 'TupleOf<number, N>' is not assignable to type 'TupleOf<number, M>'.
21+
Type 'number extends N ? number[] : _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
22+
Type 'number[] | _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
23+
Type 'number[]' is not assignable to type 'TupleOf<number, M>'.
24+
tests/cases/compiler/recursiveConditionalTypes.ts(116,5): error TS2589: Type instantiation is excessively deep and possibly infinite.
25+
tests/cases/compiler/recursiveConditionalTypes.ts(116,9): error TS2345: Argument of type 'Grow2<[], T>' is not assignable to parameter of type 'Grow1<[], T>'.
26+
Type '[] | Grow2<[string], T>' is not assignable to type 'Grow1<[], T>'.
27+
Type '[]' is not assignable to type 'Grow1<[], T>'.
28+
Type 'Grow2<[string], T>' is not assignable to type 'Grow1<[number], T>'.
29+
Type '[string] | Grow2<[string, string], T>' is not assignable to type 'Grow1<[number], T>'.
30+
Type '[string]' is not assignable to type 'Grow1<[number], T>'.
31+
Type '[string]' is not assignable to type '[number]'.
32+
Type 'string' is not assignable to type 'number'.
33+
34+
35+
==== tests/cases/compiler/recursiveConditionalTypes.ts (10 errors) ====
36+
// Awaiting promises
37+
38+
type Awaited<T> =
39+
T extends null | undefined ? T :
40+
T extends PromiseLike<infer U> ? Awaited<U> :
41+
T;
42+
43+
type MyPromise<T> = {
44+
then<U>(f: ((value: T) => U | PromiseLike<U>) | null | undefined): MyPromise<U>;
45+
}
46+
47+
type InfinitePromise<T> = Promise<InfinitePromise<T>>;
48+
49+
type P0 = Awaited<Promise<string | Promise<MyPromise<number> | null> | undefined>>;
50+
type P1 = Awaited<any>;
51+
type P2 = Awaited<InfinitePromise<number>>; // Error
52+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53+
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
54+
55+
function f11<T, U extends T>(tx: T, ta: Awaited<T>, ux: U, ua: Awaited<U>) {
56+
ta = ua;
57+
ua = ta; // Error
58+
~~
59+
!!! error TS2322: Type 'Awaited<T>' is not assignable to type 'Awaited<U>'.
60+
!!! error TS2322: Type 'T' is not assignable to type 'U'.
61+
!!! error TS2322: 'U' could be instantiated with an arbitrary type which could be unrelated to 'T'.
62+
ta = tx; // Error
63+
~~
64+
!!! error TS2322: Type 'T' is not assignable to type 'Awaited<T>'.
65+
tx = ta; // Error
66+
~~
67+
!!! error TS2322: Type 'Awaited<T>' is not assignable to type 'T'.
68+
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'Awaited<T>'.
69+
!!! error TS2322: Type 'T | (T extends PromiseLike<infer U> ? Awaited<U> : T)' is not assignable to type 'T'.
70+
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'T | (T extends PromiseLike<infer U> ? Awaited<U> : T)'.
71+
!!! error TS2322: Type 'T extends PromiseLike<infer U> ? Awaited<U> : T' is not assignable to type 'T'.
72+
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'T extends PromiseLike<infer U> ? Awaited<U> : T'.
73+
!!! error TS2322: Type 'unknown' is not assignable to type 'T'.
74+
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'unknown'.
75+
}
76+
77+
// Flattening arrays
78+
79+
type Flatten<T extends readonly unknown[]> = T extends unknown[] ? _Flatten<T>[] : readonly _Flatten<T>[];
80+
type _Flatten<T> = T extends readonly (infer U)[] ? _Flatten<U> : T;
81+
82+
type InfiniteArray<T> = InfiniteArray<T>[];
83+
84+
type B0 = Flatten<string[][][]>;
85+
type B1 = Flatten<string[][] | readonly (number[] | boolean[][])[]>;
86+
type B2 = Flatten<InfiniteArray<string>>;
87+
type B3 = B2[0]; // Error
88+
~~~~~
89+
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
90+
91+
// Repeating tuples
92+
93+
type TupleOf<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
94+
type _TupleOf<T, N extends number, R extends unknown[]> = R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;
95+
96+
type TT0 = TupleOf<string, 4>;
97+
type TT1 = TupleOf<number, 0 | 2 | 4>;
98+
type TT2 = TupleOf<number, number>;
99+
type TT3 = TupleOf<number, any>;
100+
type TT4 = TupleOf<number, 100>; // Depth error
101+
~~~~~~~~~~~~~~~~~~~~
102+
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
103+
104+
function f22<N extends number, M extends N>(tn: TupleOf<number, N>, tm: TupleOf<number, M>) {
105+
tn = tm;
106+
~~
107+
!!! error TS2322: Type 'TupleOf<number, M>' is not assignable to type 'TupleOf<number, N>'.
108+
!!! error TS2322: Type 'number extends M ? number[] : _TupleOf<number, M, []>' is not assignable to type 'TupleOf<number, N>'.
109+
!!! error TS2322: Type 'number[] | _TupleOf<number, M, []>' is not assignable to type 'TupleOf<number, N>'.
110+
!!! error TS2322: Type 'number[]' is not assignable to type 'TupleOf<number, N>'.
111+
tm = tn;
112+
~~
113+
!!! error TS2322: Type 'TupleOf<number, N>' is not assignable to type 'TupleOf<number, M>'.
114+
!!! error TS2322: Type 'number extends N ? number[] : _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
115+
!!! error TS2322: Type 'number[] | _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
116+
!!! error TS2322: Type 'number[]' is not assignable to type 'TupleOf<number, M>'.
117+
}
118+
119+
declare function f23<T>(t: TupleOf<T, 3>): T;
120+
121+
f23(['a', 'b', 'c']); // string
122+
123+
// Inference to recursive type
124+
125+
interface Box<T> { value: T };
126+
type RecBox<T> = T | Box<RecBox<T>>;
127+
type InfBox<T> = Box<InfBox<T>>;
128+
129+
declare function unbox<T>(box: RecBox<T>): T
130+
131+
type T1 = Box<string>;
132+
type T2 = Box<T1>;
133+
type T3 = Box<T2>;
134+
type T4 = Box<T3>;
135+
type T5 = Box<T4>;
136+
type T6 = Box<T5>;
137+
138+
declare let b1: Box<Box<Box<Box<Box<Box<string>>>>>>;
139+
declare let b2: T6;
140+
declare let b3: InfBox<string>;
141+
declare let b4: { value: { value: { value: typeof b4 }}};
142+
143+
unbox(b1); // string
144+
unbox(b2); // string
145+
unbox(b3); // InfBox<string>
146+
unbox({ value: { value: { value: { value: { value: { value: 5 }}}}}}); // number
147+
unbox(b4); // { value: { value: typeof b4 }}
148+
unbox({ value: { value: { get value() { return this; } }}}); // { readonly value: ... }
149+
150+
// Inference from nested instantiations of same generic types
151+
152+
type Box1<T> = { value: T };
153+
type Box2<T> = { value: T };
154+
155+
declare function foo<T>(x: Box1<Box1<T>>): T;
156+
157+
declare let z: Box2<Box2<string>>;
158+
159+
foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)
160+
161+
// Intersect tuple element types
162+
163+
type Intersect<U extends any[], R = unknown> = U extends [infer H, ...infer T] ? Intersect<T, R & H> : R;
164+
165+
type QQ = Intersect<[string[], number[], 7]>;
166+
167+
// Infer between structurally identical recursive conditional types
168+
169+
type Unpack1<T> = T extends (infer U)[] ? Unpack1<U> : T;
170+
type Unpack2<T> = T extends (infer U)[] ? Unpack2<U> : T;
171+
172+
function f20<T, U extends T>(x: Unpack1<T>, y: Unpack2<T>) {
173+
x = y;
174+
y = x;
175+
f20(y, x);
176+
}
177+
178+
type Grow1<T extends unknown[], N extends number> = T['length'] extends N ? T : Grow1<[number, ...T], N>;
179+
type Grow2<T extends unknown[], N extends number> = T['length'] extends N ? T : Grow2<[string, ...T], N>;
180+
181+
function f21<T extends number>(x: Grow1<[], T>, y: Grow2<[], T>) {
182+
f21(y, x); // Error
183+
~~~~~~~~~
184+
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
185+
~
186+
!!! error TS2345: Argument of type 'Grow2<[], T>' is not assignable to parameter of type 'Grow1<[], T>'.
187+
!!! error TS2345: Type '[] | Grow2<[string], T>' is not assignable to type 'Grow1<[], T>'.
188+
!!! error TS2345: Type '[]' is not assignable to type 'Grow1<[], T>'.
189+
!!! error TS2345: Type 'Grow2<[string], T>' is not assignable to type 'Grow1<[number], T>'.
190+
!!! error TS2345: Type '[string] | Grow2<[string, string], T>' is not assignable to type 'Grow1<[number], T>'.
191+
!!! error TS2345: Type '[string]' is not assignable to type 'Grow1<[number], T>'.
192+
!!! error TS2345: Type '[string]' is not assignable to type '[number]'.
193+
!!! error TS2345: Type 'string' is not assignable to type 'number'.
194+
}
195+

0 commit comments

Comments
 (0)