Improve checks for infinitely expanding recursive conditional types (#46326)

* Improve checks for infinitely expanding recursive conditional types

* Accept new baselines

* Add regression tests

* Remove 'export' modifier

* Accept new baselines
This commit is contained in:
Anders Hejlsberg 2021-10-13 07:08:01 -07:00 committed by GitHub
parent 315b807489
commit 0d2aeb7c65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 307 additions and 47 deletions

View file

@ -19024,6 +19024,12 @@ namespace ts {
}
}
else if (target.flags & TypeFlags.Conditional) {
// If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive
// conditional type and bail out with a Ternary.Maybe result.
if (isDeeplyNestedType(target, targetStack, targetDepth, 10)) {
resetErrorInfo(saveErrorInfo);
return Ternary.Maybe;
}
const c = target as ConditionalType;
// Check if the conditional is always true or always false but still deferred for distribution purposes
const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType));
@ -19123,33 +19129,34 @@ namespace ts {
}
}
else if (source.flags & TypeFlags.Conditional) {
// If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive
// conditional type and bail out with a Ternary.Maybe result.
if (isDeeplyNestedType(source, sourceStack, sourceDepth, 10)) {
resetErrorInfo(saveErrorInfo);
return Ternary.Maybe;
}
if (target.flags & TypeFlags.Conditional) {
// If one of the conditionals under comparison seems to be infinitely expanding, stop comparing it - back out, try
// the constraint, and failing that, give up trying to relate the two. This is the only way we can handle recursive conditional
// types, which might expand forever.
if (!isDeeplyNestedType(source, sourceStack, sourceDepth) && !isDeeplyNestedType(target, targetStack, targetDepth)) {
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.
const sourceParams = (source as ConditionalType).root.inferTypeParameters;
let sourceExtends = (source as ConditionalType).extendsType;
let mapper: TypeMapper | undefined;
if (sourceParams) {
// If the source has infer type parameters, we instantiate them in the context of the target
const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedToWorker);
inferTypes(ctx.inferences, (target as ConditionalType).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
sourceExtends = instantiateType(sourceExtends, ctx.mapper);
mapper = ctx.mapper;
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.
const sourceParams = (source as ConditionalType).root.inferTypeParameters;
let sourceExtends = (source as ConditionalType).extendsType;
let mapper: TypeMapper | undefined;
if (sourceParams) {
// If the source has infer type parameters, we instantiate them in the context of the target
const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedToWorker);
inferTypes(ctx.inferences, (target as ConditionalType).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
sourceExtends = instantiateType(sourceExtends, ctx.mapper);
mapper = ctx.mapper;
}
if (isTypeIdenticalTo(sourceExtends, (target as ConditionalType).extendsType) &&
(isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both) || isRelatedTo((target as ConditionalType).checkType, (source as ConditionalType).checkType, RecursionFlags.Both))) {
if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source as ConditionalType), mapper), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors)) {
result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors);
}
if (isTypeIdenticalTo(sourceExtends, (target as ConditionalType).extendsType) &&
(isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both) || isRelatedTo((target as ConditionalType).checkType, (source as ConditionalType).checkType, RecursionFlags.Both))) {
if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source as ConditionalType), mapper), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors)) {
result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors);
}
if (result) {
resetErrorInfo(saveErrorInfo);
return result;
}
if (result) {
resetErrorInfo(saveErrorInfo);
return result;
}
}
}
@ -19165,18 +19172,13 @@ namespace ts {
}
}
// We'll repeatedly decompose source side conditionals if they're recursive - check if we've already recured on the constraint a lot and, if so, bail
// on the comparison.
if (!isDeeplyNestedType(source, sourceStack, sourceDepth)) {
// conditionals _can_ be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O`
// when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!).
const defaultConstraint = getDefaultConstraintOfConditionalType(source as ConditionalType);
if (defaultConstraint) {
if (result = isRelatedTo(defaultConstraint, target, RecursionFlags.Source, reportErrors)) {
resetErrorInfo(saveErrorInfo);
return result;
}
// conditionals _can_ be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O`
// when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!).
const defaultConstraint = getDefaultConstraintOfConditionalType(source as ConditionalType);
if (defaultConstraint) {
if (result = isRelatedTo(defaultConstraint, target, RecursionFlags.Source, reportErrors)) {
resetErrorInfo(saveErrorInfo);
return result;
}
}
}
@ -20322,14 +20324,14 @@ namespace ts {
// `type A<T> = null extends T ? [A<NonNullable<T>>] : [T]`
// has expanded into `[A<NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>>>>>>]`
// in such cases we need to terminate the expansion, and we do so here.
function isDeeplyNestedType(type: Type, stack: Type[], depth: number): boolean {
if (depth >= 5) {
function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 5): boolean {
if (depth >= maxDepth) {
const identity = getRecursionIdentity(type);
let count = 0;
for (let i = 0; i < depth; i++) {
if (getRecursionIdentity(stack[i]) === identity) {
count++;
if (count >= 5) {
if (count >= maxDepth) {
return true;
}
}

View file

@ -8,7 +8,8 @@ tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcre
Type 'ReturnType<T[string]>' is not assignable to type 'A'.
Type 'unknown' is not assignable to type 'A'.
Type 'ReturnType<FunctionsObj<T>[string]>' is not assignable to type 'A'.
Property 'x' is missing in type '{}' but required in type 'A'.
Type 'unknown' is not assignable to type 'A'.
Property 'x' is missing in type '{}' but required in type 'A'.
==== tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts (1 errors) ====
@ -36,7 +37,8 @@ tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcre
!!! error TS2322: Type 'ReturnType<T[string]>' is not assignable to type 'A'.
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
!!! error TS2322: Type 'ReturnType<FunctionsObj<T>[string]>' is not assignable to type 'A'.
!!! error TS2322: Property 'x' is missing in type '{}' but required in type 'A'.
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
!!! error TS2322: Property 'x' is missing in type '{}' but required in type 'A'.
!!! related TS2728 tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts:1:15: 'x' is declared here.
}

View file

@ -2,10 +2,9 @@ tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' ca
tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Value<"dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Value<"dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
tests/cases/compiler/infiniteConstraints.ts(48,27): error TS2321: Excessive stack depth comparing types 'Conv<ExactExtract<U, T>, ExactExtract<U, T>>' and 'unknown[]'.
==== tests/cases/compiler/infiniteConstraints.ts (5 errors) ====
==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ====
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
@ -64,6 +63,4 @@ tests/cases/compiler/infiniteConstraints.ts(48,27): error TS2321: Excessive stac
type Conv<T, U = T> =
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];
~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2321: Excessive stack depth comparing types 'Conv<ExactExtract<U, T>, ExactExtract<U, T>>' and 'unknown[]'.

View file

@ -23,9 +23,10 @@ tests/cases/compiler/recursiveConditionalTypes.ts(117,9): error TS2345: Argument
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(169,5): error TS2322: Type 'number' is not assignable to type 'Enumerate<T["length"]>'.
==== tests/cases/compiler/recursiveConditionalTypes.ts (9 errors) ====
==== tests/cases/compiler/recursiveConditionalTypes.ts (10 errors) ====
// Awaiting promises
type __Awaited<T> =
@ -198,4 +199,38 @@ tests/cases/compiler/recursiveConditionalTypes.ts(117,9): error TS2345: Argument
type Helper<T> = T extends ParseSuccess<infer R> ? ParseSuccess<R> : null
type TP2 = ParseManyWhitespace2<" foo">;
// Repro from #46183
type NTuple<N extends number, Tup extends unknown[] = []> =
Tup['length'] extends N ? Tup : NTuple<N, [...Tup, unknown]>;
type Add<A extends number, B extends number> =
[...NTuple<A>, ...NTuple<B>]['length'];
let five: Add<2, 3>;
// Repro from #46316
type _PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T
? [T, ...A] extends [...infer X]
? X
: never
: never;
type _Enumerate<A extends Array<unknown>, N extends number> = N extends A['length']
? A
: _Enumerate<_PrependNextNum<A>, N> & number;
type Enumerate<N extends number> = number extends N
? number
: _Enumerate<[], N> extends (infer E)[]
? E
: never;
function foo2<T extends unknown[]>(value: T): Enumerate<T['length']> {
return value.length; // Error
~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'Enumerate<T["length"]>'.
}

View file

@ -137,6 +137,38 @@ type ParseManyWhitespace2<S extends string> =
type Helper<T> = T extends ParseSuccess<infer R> ? ParseSuccess<R> : null
type TP2 = ParseManyWhitespace2<" foo">;
// Repro from #46183
type NTuple<N extends number, Tup extends unknown[] = []> =
Tup['length'] extends N ? Tup : NTuple<N, [...Tup, unknown]>;
type Add<A extends number, B extends number> =
[...NTuple<A>, ...NTuple<B>]['length'];
let five: Add<2, 3>;
// Repro from #46316
type _PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T
? [T, ...A] extends [...infer X]
? X
: never
: never;
type _Enumerate<A extends Array<unknown>, N extends number> = N extends A['length']
? A
: _Enumerate<_PrependNextNum<A>, N> & number;
type Enumerate<N extends number> = number extends N
? number
: _Enumerate<[], N> extends (infer E)[]
? E
: never;
function foo2<T extends unknown[]>(value: T): Enumerate<T['length']> {
return value.length; // Error
}
//// [recursiveConditionalTypes.js]
@ -169,6 +201,10 @@ function f20(x, y) {
function f21(x, y) {
f21(y, x); // Error
}
let five;
function foo2(value) {
return value.length; // Error
}
//// [recursiveConditionalTypes.d.ts]
@ -244,3 +280,13 @@ declare type TP1 = ParseManyWhitespace<" foo">;
declare type ParseManyWhitespace2<S extends string> = S extends ` ${infer R0}` ? Helper<ParseManyWhitespace2<R0>> : ParseSuccess<S>;
declare type Helper<T> = T extends ParseSuccess<infer R> ? ParseSuccess<R> : null;
declare type TP2 = ParseManyWhitespace2<" foo">;
declare type NTuple<N extends number, Tup extends unknown[] = []> = Tup['length'] extends N ? Tup : NTuple<N, [...Tup, unknown]>;
declare type Add<A extends number, B extends number> = [
...NTuple<A>,
...NTuple<B>
]['length'];
declare let five: Add<2, 3>;
declare type _PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T ? [T, ...A] extends [...infer X] ? X : never : never;
declare type _Enumerate<A extends Array<unknown>, N extends number> = N extends A['length'] ? A : _Enumerate<_PrependNextNum<A>, N> & number;
declare type Enumerate<N extends number> = number extends N ? number : _Enumerate<[], N> extends (infer E)[] ? E : never;
declare function foo2<T extends unknown[]>(value: T): Enumerate<T['length']>;

View file

@ -531,3 +531,100 @@ type TP2 = ParseManyWhitespace2<" foo">;
>TP2 : Symbol(TP2, Decl(recursiveConditionalTypes.ts, 135, 73))
>ParseManyWhitespace2 : Symbol(ParseManyWhitespace2, Decl(recursiveConditionalTypes.ts, 128, 39))
// Repro from #46183
type NTuple<N extends number, Tup extends unknown[] = []> =
>NTuple : Symbol(NTuple, Decl(recursiveConditionalTypes.ts, 137, 40))
>N : Symbol(N, Decl(recursiveConditionalTypes.ts, 141, 12))
>Tup : Symbol(Tup, Decl(recursiveConditionalTypes.ts, 141, 29))
Tup['length'] extends N ? Tup : NTuple<N, [...Tup, unknown]>;
>Tup : Symbol(Tup, Decl(recursiveConditionalTypes.ts, 141, 29))
>N : Symbol(N, Decl(recursiveConditionalTypes.ts, 141, 12))
>Tup : Symbol(Tup, Decl(recursiveConditionalTypes.ts, 141, 29))
>NTuple : Symbol(NTuple, Decl(recursiveConditionalTypes.ts, 137, 40))
>N : Symbol(N, Decl(recursiveConditionalTypes.ts, 141, 12))
>Tup : Symbol(Tup, Decl(recursiveConditionalTypes.ts, 141, 29))
type Add<A extends number, B extends number> =
>Add : Symbol(Add, Decl(recursiveConditionalTypes.ts, 142, 65))
>A : Symbol(A, Decl(recursiveConditionalTypes.ts, 144, 9))
>B : Symbol(B, Decl(recursiveConditionalTypes.ts, 144, 26))
[...NTuple<A>, ...NTuple<B>]['length'];
>NTuple : Symbol(NTuple, Decl(recursiveConditionalTypes.ts, 137, 40))
>A : Symbol(A, Decl(recursiveConditionalTypes.ts, 144, 9))
>NTuple : Symbol(NTuple, Decl(recursiveConditionalTypes.ts, 137, 40))
>B : Symbol(B, Decl(recursiveConditionalTypes.ts, 144, 26))
let five: Add<2, 3>;
>five : Symbol(five, Decl(recursiveConditionalTypes.ts, 147, 3))
>Add : Symbol(Add, Decl(recursiveConditionalTypes.ts, 142, 65))
// Repro from #46316
type _PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T
>_PrependNextNum : Symbol(_PrependNextNum, Decl(recursiveConditionalTypes.ts, 147, 20))
>A : Symbol(A, Decl(recursiveConditionalTypes.ts, 151, 21))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 2 more)
>A : Symbol(A, Decl(recursiveConditionalTypes.ts, 151, 21))
>T : Symbol(T, Decl(recursiveConditionalTypes.ts, 151, 74))
? [T, ...A] extends [...infer X]
>T : Symbol(T, Decl(recursiveConditionalTypes.ts, 151, 74))
>A : Symbol(A, Decl(recursiveConditionalTypes.ts, 151, 21))
>X : Symbol(X, Decl(recursiveConditionalTypes.ts, 152, 33))
? X
>X : Symbol(X, Decl(recursiveConditionalTypes.ts, 152, 33))
: never
: never;
type _Enumerate<A extends Array<unknown>, N extends number> = N extends A['length']
>_Enumerate : Symbol(_Enumerate, Decl(recursiveConditionalTypes.ts, 155, 12))
>A : Symbol(A, Decl(recursiveConditionalTypes.ts, 157, 16))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 2 more)
>N : Symbol(N, Decl(recursiveConditionalTypes.ts, 157, 41))
>N : Symbol(N, Decl(recursiveConditionalTypes.ts, 157, 41))
>A : Symbol(A, Decl(recursiveConditionalTypes.ts, 157, 16))
? A
>A : Symbol(A, Decl(recursiveConditionalTypes.ts, 157, 16))
: _Enumerate<_PrependNextNum<A>, N> & number;
>_Enumerate : Symbol(_Enumerate, Decl(recursiveConditionalTypes.ts, 155, 12))
>_PrependNextNum : Symbol(_PrependNextNum, Decl(recursiveConditionalTypes.ts, 147, 20))
>A : Symbol(A, Decl(recursiveConditionalTypes.ts, 157, 16))
>N : Symbol(N, Decl(recursiveConditionalTypes.ts, 157, 41))
type Enumerate<N extends number> = number extends N
>Enumerate : Symbol(Enumerate, Decl(recursiveConditionalTypes.ts, 159, 49))
>N : Symbol(N, Decl(recursiveConditionalTypes.ts, 161, 15))
>N : Symbol(N, Decl(recursiveConditionalTypes.ts, 161, 15))
? number
: _Enumerate<[], N> extends (infer E)[]
>_Enumerate : Symbol(_Enumerate, Decl(recursiveConditionalTypes.ts, 155, 12))
>N : Symbol(N, Decl(recursiveConditionalTypes.ts, 161, 15))
>E : Symbol(E, Decl(recursiveConditionalTypes.ts, 163, 38))
? E
>E : Symbol(E, Decl(recursiveConditionalTypes.ts, 163, 38))
: never;
function foo2<T extends unknown[]>(value: T): Enumerate<T['length']> {
>foo2 : Symbol(foo2, Decl(recursiveConditionalTypes.ts, 165, 12))
>T : Symbol(T, Decl(recursiveConditionalTypes.ts, 167, 14))
>value : Symbol(value, Decl(recursiveConditionalTypes.ts, 167, 35))
>T : Symbol(T, Decl(recursiveConditionalTypes.ts, 167, 14))
>Enumerate : Symbol(Enumerate, Decl(recursiveConditionalTypes.ts, 159, 49))
>T : Symbol(T, Decl(recursiveConditionalTypes.ts, 167, 14))
return value.length; // Error
>value.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>value : Symbol(value, Decl(recursiveConditionalTypes.ts, 167, 35))
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
}

View file

@ -345,3 +345,52 @@ type Helper<T> = T extends ParseSuccess<infer R> ? ParseSuccess<R> : null
type TP2 = ParseManyWhitespace2<" foo">;
>TP2 : ParseSuccess<"foo">
// Repro from #46183
type NTuple<N extends number, Tup extends unknown[] = []> =
>NTuple : NTuple<N, Tup>
Tup['length'] extends N ? Tup : NTuple<N, [...Tup, unknown]>;
type Add<A extends number, B extends number> =
>Add : Add<A, B>
[...NTuple<A>, ...NTuple<B>]['length'];
let five: Add<2, 3>;
>five : 5
// Repro from #46316
type _PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T
>_PrependNextNum : _PrependNextNum<A>
? [T, ...A] extends [...infer X]
? X
: never
: never;
type _Enumerate<A extends Array<unknown>, N extends number> = N extends A['length']
>_Enumerate : _Enumerate<A, N>
? A
: _Enumerate<_PrependNextNum<A>, N> & number;
type Enumerate<N extends number> = number extends N
>Enumerate : Enumerate<N>
? number
: _Enumerate<[], N> extends (infer E)[]
? E
: never;
function foo2<T extends unknown[]>(value: T): Enumerate<T['length']> {
>foo2 : <T extends unknown[]>(value: T) => Enumerate<T['length']>
>value : T
return value.length; // Error
>value.length : number
>value : T
>length : number
}

View file

@ -140,3 +140,35 @@ type ParseManyWhitespace2<S extends string> =
type Helper<T> = T extends ParseSuccess<infer R> ? ParseSuccess<R> : null
type TP2 = ParseManyWhitespace2<" foo">;
// Repro from #46183
type NTuple<N extends number, Tup extends unknown[] = []> =
Tup['length'] extends N ? Tup : NTuple<N, [...Tup, unknown]>;
type Add<A extends number, B extends number> =
[...NTuple<A>, ...NTuple<B>]['length'];
let five: Add<2, 3>;
// Repro from #46316
type _PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T
? [T, ...A] extends [...infer X]
? X
: never
: never;
type _Enumerate<A extends Array<unknown>, N extends number> = N extends A['length']
? A
: _Enumerate<_PrependNextNum<A>, N> & number;
type Enumerate<N extends number> = number extends N
? number
: _Enumerate<[], N> extends (infer E)[]
? E
: never;
function foo2<T extends unknown[]>(value: T): Enumerate<T['length']> {
return value.length; // Error
}