Skip to content

Recursive conditional types #40002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 16, 2020
185 changes: 78 additions & 107 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5292,8 +5292,6 @@ namespace ts {
node: ConditionalTypeNode;
checkType: Type;
extendsType: Type;
trueType: Type;
falseType: Type;
isDistributive: boolean;
inferTypeParameters?: TypeParameter[];
outerTypeParameters?: TypeParameter[];
Expand Down
2 changes: 0 additions & 2 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2605,8 +2605,6 @@ declare namespace ts {
node: ConditionalTypeNode;
checkType: Type;
extendsType: Type;
trueType: Type;
falseType: Type;
isDistributive: boolean;
inferTypeParameters?: TypeParameter[];
outerTypeParameters?: TypeParameter[];
Expand Down
2 changes: 0 additions & 2 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2605,8 +2605,6 @@ declare namespace ts {
node: ConditionalTypeNode;
checkType: Type;
extendsType: Type;
trueType: Type;
falseType: Type;
isDistributive: boolean;
inferTypeParameters?: TypeParameter[];
outerTypeParameters?: TypeParameter[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,5 @@ type T0 = Foo<99>;
>T0 : "99"

type T1 = Foo<any>;
>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"
>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"

4 changes: 2 additions & 2 deletions tests/baselines/reference/promiseTypeInference.types
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ declare function convert(s: string): IPromise<number>;
>s : string

var $$x = load("something").then(s => convert(s));
>$$x : CPromise<unknown>
>load("something").then(s => convert(s)) : CPromise<unknown>
>$$x : CPromise<number>
>load("something").then(s => convert(s)) : CPromise<number>
>load("something").then : <U>(success?: (value: string) => CPromise<U>) => CPromise<U>
>load("something") : CPromise<string>
>load : (name: string) => CPromise<string>
Expand Down
168 changes: 168 additions & 0 deletions tests/baselines/reference/recursiveConditionalTypes.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
tests/cases/compiler/recursiveConditionalTypes.ts(16,11): error TS2589: Type instantiation is excessively deep and possibly infinite.
tests/cases/compiler/recursiveConditionalTypes.ts(20,5): error TS2322: Type 'Awaited<T>' is not assignable to type 'Awaited<U>'.
Type 'T' is not assignable to type 'U'.
'U' could be instantiated with an arbitrary type which could be unrelated to 'T'.
tests/cases/compiler/recursiveConditionalTypes.ts(21,5): error TS2322: Type 'T' is not assignable to type 'Awaited<T>'.
tests/cases/compiler/recursiveConditionalTypes.ts(22,5): error TS2322: Type 'Awaited<T>' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'Awaited<T>'.
Type 'T | (T extends PromiseLike<infer U> ? Awaited<U> : T)' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'T | (T extends PromiseLike<infer U> ? Awaited<U> : T)'.
Type 'T extends PromiseLike<infer U> ? Awaited<U> : T' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'T extends PromiseLike<infer U> ? Awaited<U> : T'.
Type 'unknown' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'unknown'.
tests/cases/compiler/recursiveConditionalTypes.ts(35,11): error TS2589: Type instantiation is excessively deep and possibly infinite.
tests/cases/compiler/recursiveConditionalTypes.ts(46,12): error TS2589: Type instantiation is excessively deep and possibly infinite.
tests/cases/compiler/recursiveConditionalTypes.ts(49,5): error TS2322: Type 'TupleOf<number, M>' is not assignable to type 'TupleOf<number, N>'.
Type 'number extends M ? number[] : _TupleOf<number, M, []>' is not assignable to type 'TupleOf<number, N>'.
Type 'number[] | _TupleOf<number, M, []>' is not assignable to type 'TupleOf<number, N>'.
Type 'number[]' is not assignable to type 'TupleOf<number, N>'.
tests/cases/compiler/recursiveConditionalTypes.ts(50,5): error TS2322: Type 'TupleOf<number, N>' is not assignable to type 'TupleOf<number, M>'.
Type 'number extends N ? number[] : _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
Type 'number[] | _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
Type 'number[]' is not assignable to type 'TupleOf<number, M>'.
tests/cases/compiler/recursiveConditionalTypes.ts(89,5): error TS2589: Type instantiation is excessively deep and possibly infinite.
tests/cases/compiler/recursiveConditionalTypes.ts(89,9): error TS2345: Argument of type 'Grow2<[], T>' is not assignable to parameter of type 'Grow1<[], T>'.
Type '[] | Grow2<[string], T>' is not assignable to type 'Grow1<[], T>'.
Type '[]' is not assignable to type 'Grow1<[], T>'.
Type 'Grow2<[string], T>' is not assignable to type 'Grow1<[number], T>'.
Type '[string] | Grow2<[string, string], T>' is not assignable to type 'Grow1<[number], T>'.
Type '[string]' is not assignable to type 'Grow1<[number], T>'.
Type '[string]' is not assignable to type '[number]'.
Type 'string' is not assignable to type 'number'.


==== tests/cases/compiler/recursiveConditionalTypes.ts (10 errors) ====
// Awaiting promises

type Awaited<T> =
T extends null | undefined ? T :
T extends PromiseLike<infer U> ? Awaited<U> :
T;

type MyPromise<T> = {
then<U>(f: ((value: T) => U | PromiseLike<U>) | null | undefined): MyPromise<U>;
}

type InfinitePromise<T> = Promise<InfinitePromise<T>>;

type P0 = Awaited<Promise<string | Promise<MyPromise<number> | null> | undefined>>;
type P1 = Awaited<any>;
type P2 = Awaited<InfinitePromise<number>>; // Error
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.

function f11<T, U extends T>(tx: T, ta: Awaited<T>, ux: U, ua: Awaited<U>) {
ta = ua;
ua = ta; // Error
~~
!!! error TS2322: Type 'Awaited<T>' is not assignable to type 'Awaited<U>'.
!!! error TS2322: Type 'T' is not assignable to type 'U'.
!!! error TS2322: 'U' could be instantiated with an arbitrary type which could be unrelated to 'T'.
ta = tx; // Error
~~
!!! error TS2322: Type 'T' is not assignable to type 'Awaited<T>'.
tx = ta; // Error
~~
!!! error TS2322: Type 'Awaited<T>' is not assignable to type 'T'.
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'Awaited<T>'.
!!! error TS2322: Type 'T | (T extends PromiseLike<infer U> ? Awaited<U> : T)' is not assignable to type 'T'.
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'T | (T extends PromiseLike<infer U> ? Awaited<U> : T)'.
!!! error TS2322: Type 'T extends PromiseLike<infer U> ? Awaited<U> : T' is not assignable to type 'T'.
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'T extends PromiseLike<infer U> ? Awaited<U> : T'.
!!! error TS2322: Type 'unknown' is not assignable to type 'T'.
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'unknown'.
}

// Flattening arrays

type Flatten<T extends readonly unknown[]> = T extends unknown[] ? _Flatten<T>[] : readonly _Flatten<T>[];
type _Flatten<T> = T extends readonly (infer U)[] ? _Flatten<U> : T;

type InfiniteArray<T> = InfiniteArray<T>[];

type B0 = Flatten<string[][][]>;
type B1 = Flatten<string[][] | readonly (number[] | boolean[][])[]>;
type B2 = Flatten<InfiniteArray<string>>;
type B3 = B2[0]; // Error
~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.

// Repeating tuples

type TupleOf<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
type _TupleOf<T, N extends number, R extends unknown[]> = R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;

type TT0 = TupleOf<string, 4>;
type TT1 = TupleOf<number, 0 | 2 | 4>;
type TT2 = TupleOf<number, number>;
type TT3 = TupleOf<number, any>;
type TT4 = TupleOf<number, 100>; // Depth error
~~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.

function f22<N extends number, M extends N>(tn: TupleOf<number, N>, tm: TupleOf<number, M>) {
tn = tm;
~~
!!! error TS2322: Type 'TupleOf<number, M>' is not assignable to type 'TupleOf<number, N>'.
!!! error TS2322: Type 'number extends M ? number[] : _TupleOf<number, M, []>' is not assignable to type 'TupleOf<number, N>'.
!!! error TS2322: Type 'number[] | _TupleOf<number, M, []>' is not assignable to type 'TupleOf<number, N>'.
!!! error TS2322: Type 'number[]' is not assignable to type 'TupleOf<number, N>'.
tm = tn;
~~
!!! error TS2322: Type 'TupleOf<number, N>' is not assignable to type 'TupleOf<number, M>'.
!!! error TS2322: Type 'number extends N ? number[] : _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
!!! error TS2322: Type 'number[] | _TupleOf<number, N, []>' is not assignable to type 'TupleOf<number, M>'.
!!! error TS2322: Type 'number[]' is not assignable to type 'TupleOf<number, M>'.
}

declare function f23<T>(t: TupleOf<T, 3>): T;

f23(['a', 'b', 'c']); // string

// Inference from nested instantiations of same generic types

type Box1<T> = { value: T };
type Box2<T> = { value: T };

declare function foo<T>(x: Box1<Box1<T>>): T;

declare let z: Box2<Box2<string>>;

foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)

// Intersect tuple element types

type Intersect<U extends any[], R = unknown> = U extends [infer H, ...infer T] ? Intersect<T, R & H> : R;

type QQ = Intersect<[string[], number[], 7]>;

// Infer between structurally identical recursive conditional types

type Unpack1<T> = T extends (infer U)[] ? Unpack1<U> : T;
type Unpack2<T> = T extends (infer U)[] ? Unpack2<U> : T;

function f20<T, U extends T>(x: Unpack1<T>, y: Unpack2<T>) {
x = y;
y = x;
f20(y, x);
}

type Grow1<T extends unknown[], N extends number> = T['length'] extends N ? T : Grow1<[number, ...T], N>;
type Grow2<T extends unknown[], N extends number> = T['length'] extends N ? T : Grow2<[string, ...T], N>;

function f21<T extends number>(x: Grow1<[], T>, y: Grow2<[], T>) {
f21(y, x); // Error
~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
~
!!! error TS2345: Argument of type 'Grow2<[], T>' is not assignable to parameter of type 'Grow1<[], T>'.
!!! error TS2345: Type '[] | Grow2<[string], T>' is not assignable to type 'Grow1<[], T>'.
!!! error TS2345: Type '[]' is not assignable to type 'Grow1<[], T>'.
!!! error TS2345: Type 'Grow2<[string], T>' is not assignable to type 'Grow1<[number], T>'.
!!! error TS2345: Type '[string] | Grow2<[string, string], T>' is not assignable to type 'Grow1<[number], T>'.
!!! error TS2345: Type '[string]' is not assignable to type 'Grow1<[number], T>'.
!!! error TS2345: Type '[string]' is not assignable to type '[number]'.
!!! error TS2345: Type 'string' is not assignable to type 'number'.
}

160 changes: 160 additions & 0 deletions tests/baselines/reference/recursiveConditionalTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//// [recursiveConditionalTypes.ts]
// Awaiting promises

type Awaited<T> =
T extends null | undefined ? T :
T extends PromiseLike<infer U> ? Awaited<U> :
T;

type MyPromise<T> = {
then<U>(f: ((value: T) => U | PromiseLike<U>) | null | undefined): MyPromise<U>;
}

type InfinitePromise<T> = Promise<InfinitePromise<T>>;

type P0 = Awaited<Promise<string | Promise<MyPromise<number> | null> | undefined>>;
type P1 = Awaited<any>;
type P2 = Awaited<InfinitePromise<number>>; // Error

function f11<T, U extends T>(tx: T, ta: Awaited<T>, ux: U, ua: Awaited<U>) {
ta = ua;
ua = ta; // Error
ta = tx; // Error
tx = ta; // Error
}

// Flattening arrays

type Flatten<T extends readonly unknown[]> = T extends unknown[] ? _Flatten<T>[] : readonly _Flatten<T>[];
type _Flatten<T> = T extends readonly (infer U)[] ? _Flatten<U> : T;

type InfiniteArray<T> = InfiniteArray<T>[];

type B0 = Flatten<string[][][]>;
type B1 = Flatten<string[][] | readonly (number[] | boolean[][])[]>;
type B2 = Flatten<InfiniteArray<string>>;
type B3 = B2[0]; // Error

// Repeating tuples

type TupleOf<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
type _TupleOf<T, N extends number, R extends unknown[]> = R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;

type TT0 = TupleOf<string, 4>;
type TT1 = TupleOf<number, 0 | 2 | 4>;
type TT2 = TupleOf<number, number>;
type TT3 = TupleOf<number, any>;
type TT4 = TupleOf<number, 100>; // Depth error

function f22<N extends number, M extends N>(tn: TupleOf<number, N>, tm: TupleOf<number, M>) {
tn = tm;
tm = tn;
}

declare function f23<T>(t: TupleOf<T, 3>): T;

f23(['a', 'b', 'c']); // string

// Inference from nested instantiations of same generic types

type Box1<T> = { value: T };
type Box2<T> = { value: T };

declare function foo<T>(x: Box1<Box1<T>>): T;

declare let z: Box2<Box2<string>>;

foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)

// Intersect tuple element types

type Intersect<U extends any[], R = unknown> = U extends [infer H, ...infer T] ? Intersect<T, R & H> : R;

type QQ = Intersect<[string[], number[], 7]>;

// Infer between structurally identical recursive conditional types

type Unpack1<T> = T extends (infer U)[] ? Unpack1<U> : T;
type Unpack2<T> = T extends (infer U)[] ? Unpack2<U> : T;

function f20<T, U extends T>(x: Unpack1<T>, y: Unpack2<T>) {
x = y;
y = x;
f20(y, x);
}

type Grow1<T extends unknown[], N extends number> = T['length'] extends N ? T : Grow1<[number, ...T], N>;
type Grow2<T extends unknown[], N extends number> = T['length'] extends N ? T : Grow2<[string, ...T], N>;

function f21<T extends number>(x: Grow1<[], T>, y: Grow2<[], T>) {
f21(y, x); // Error
}


//// [recursiveConditionalTypes.js]
"use strict";
// Awaiting promises
function f11(tx, ta, ux, ua) {
ta = ua;
ua = ta; // Error
ta = tx; // Error
tx = ta; // Error
}
function f22(tn, tm) {
tn = tm;
tm = tn;
}
f23(['a', 'b', 'c']); // string
foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)
function f20(x, y) {
x = y;
y = x;
f20(y, x);
}
function f21(x, y) {
f21(y, x); // Error
}


//// [recursiveConditionalTypes.d.ts]
declare type Awaited<T> = T extends null | undefined ? T : T extends PromiseLike<infer U> ? Awaited<U> : T;
declare type MyPromise<T> = {
then<U>(f: ((value: T) => U | PromiseLike<U>) | null | undefined): MyPromise<U>;
};
declare type InfinitePromise<T> = Promise<InfinitePromise<T>>;
declare type P0 = Awaited<Promise<string | Promise<MyPromise<number> | null> | undefined>>;
declare type P1 = Awaited<any>;
declare type P2 = Awaited<InfinitePromise<number>>;
declare function f11<T, U extends T>(tx: T, ta: Awaited<T>, ux: U, ua: Awaited<U>): void;
declare type Flatten<T extends readonly unknown[]> = T extends unknown[] ? _Flatten<T>[] : readonly _Flatten<T>[];
declare type _Flatten<T> = T extends readonly (infer U)[] ? _Flatten<U> : T;
declare type InfiniteArray<T> = InfiniteArray<T>[];
declare type B0 = Flatten<string[][][]>;
declare type B1 = Flatten<string[][] | readonly (number[] | boolean[][])[]>;
declare type B2 = Flatten<InfiniteArray<string>>;
declare type B3 = B2[0];
declare type TupleOf<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
declare type _TupleOf<T, N extends number, R extends unknown[]> = R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;
declare type TT0 = TupleOf<string, 4>;
declare type TT1 = TupleOf<number, 0 | 2 | 4>;
declare type TT2 = TupleOf<number, number>;
declare type TT3 = TupleOf<number, any>;
declare type TT4 = TupleOf<number, 100>;
declare function f22<N extends number, M extends N>(tn: TupleOf<number, N>, tm: TupleOf<number, M>): void;
declare function f23<T>(t: TupleOf<T, 3>): T;
declare type Box1<T> = {
value: T;
};
declare type Box2<T> = {
value: T;
};
declare function foo<T>(x: Box1<Box1<T>>): T;
declare let z: Box2<Box2<string>>;
declare type Intersect<U extends any[], R = unknown> = U extends [infer H, ...infer T] ? Intersect<T, R & H> : R;
declare type QQ = Intersect<[string[], number[], 7]>;
declare type Unpack1<T> = T extends (infer U)[] ? Unpack1<U> : T;
declare type Unpack2<T> = T extends (infer U)[] ? Unpack2<U> : T;
declare function f20<T, U extends T>(x: Unpack1<T>, y: Unpack2<T>): void;
declare type Grow1<T extends unknown[], N extends number> = T['length'] extends N ? T : Grow1<[number, ...T], N>;
declare type Grow2<T extends unknown[], N extends number> = T['length'] extends N ? T : Grow2<[string, ...T], N>;
declare function f21<T extends number>(x: Grow1<[], T>, y: Grow2<[], T>): void;
Loading