Fix recursive type inference (#53396)

This commit is contained in:
Anders Hejlsberg 2023-03-21 12:53:16 -07:00 committed by GitHub
parent bace6897e2
commit 3d2c3442db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 143 additions and 19 deletions

View File

@ -22911,7 +22911,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type.symbol;
}
if (isTupleType(type)) {
return type;
return type.target;
}
}
if (type.flags & TypeFlags.TypeParameter) {
@ -24273,8 +24273,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
let inferencePriority: number = InferencePriority.MaxValue;
let allowComplexConstraintInference = true;
let visited: Map<string, number>;
let sourceStack: object[];
let targetStack: object[];
let sourceStack: Type[];
let targetStack: Type[];
let expandingFlags = ExpandingFlags.None;
inferFromTypes(originalSource, originalTarget);
@ -24530,20 +24530,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// We stop inferring and report a circularity if we encounter duplicate recursion identities on both
// the source side and the target side.
const saveExpandingFlags = expandingFlags;
const sourceIdentity = getRecursionIdentity(source);
const targetIdentity = getRecursionIdentity(target);
if (contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source;
if (contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target;
(sourceStack ??= []).push(source);
(targetStack ??= []).push(target);
if (isDeeplyNestedType(source, sourceStack, sourceStack.length, 2)) expandingFlags |= ExpandingFlags.Source;
if (isDeeplyNestedType(target, targetStack, targetStack.length, 2)) expandingFlags |= ExpandingFlags.Target;
if (expandingFlags !== ExpandingFlags.Both) {
(sourceStack || (sourceStack = [])).push(sourceIdentity);
(targetStack || (targetStack = [])).push(targetIdentity);
action(source, target);
targetStack.pop();
sourceStack.pop();
}
else {
inferencePriority = InferencePriority.Circularity;
}
targetStack.pop();
sourceStack.pop();
expandingFlags = saveExpandingFlags;
visited.set(key, inferencePriority);
inferencePriority = Math.min(inferencePriority, saveInferencePriority);

View File

@ -0,0 +1,54 @@
tests/cases/compiler/inferFromNestedSameShapeTuple.ts(42,5): error TS2322: Type 'T1<U>' is not assignable to type 'T2<U>'.
Type at position 0 in source is not compatible with type at position 0 in target.
Type 'number' is not assignable to type '42'.
==== tests/cases/compiler/inferFromNestedSameShapeTuple.ts (1 errors) ====
// repro #48524
type Magic<X> = X extends [[infer Y, ...infer _], ...infer __] ? Y : never;
type R = Magic<[[number]]>
// repro #52722
type Recursive<Id> = {
id: Id
children: readonly Recursive<Id>[]
}
declare function getIds<Id>(items: readonly Recursive<Id>[]): Id[];
const items = [{
id: 'a',
children: [{
id: 'b',
children: []
}]
}] as const satisfies readonly Recursive<string>[]
const foo = getIds(items)
// variant with a fresh argument
const foo2 = getIds([{
id: 'a',
children: [{
id: 'b',
children: []
}]
}] as const)
// Repro from comment in #49226
type T1<T> = [number, T1<{ x: T }>];
type T2<T> = [42, T2<{ x: T }>];
function qq<U>(x: T1<U>, y: T2<U>) {
x = y;
y = x; // Error
~
!!! error TS2322: Type 'T1<U>' is not assignable to type 'T2<U>'.
!!! error TS2322: Type at position 0 in source is not compatible with type at position 0 in target.
!!! error TS2322: Type 'number' is not assignable to type '42'.
}

View File

@ -84,3 +84,38 @@ const foo2 = getIds([{
}] as const)
>const : Symbol(const)
// Repro from comment in #49226
type T1<T> = [number, T1<{ x: T }>];
>T1 : Symbol(T1, Decl(inferFromNestedSameShapeTuple.ts, 32, 12))
>T : Symbol(T, Decl(inferFromNestedSameShapeTuple.ts, 36, 8))
>T1 : Symbol(T1, Decl(inferFromNestedSameShapeTuple.ts, 32, 12))
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 36, 26))
>T : Symbol(T, Decl(inferFromNestedSameShapeTuple.ts, 36, 8))
type T2<T> = [42, T2<{ x: T }>];
>T2 : Symbol(T2, Decl(inferFromNestedSameShapeTuple.ts, 36, 36))
>T : Symbol(T, Decl(inferFromNestedSameShapeTuple.ts, 37, 8))
>T2 : Symbol(T2, Decl(inferFromNestedSameShapeTuple.ts, 36, 36))
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 37, 22))
>T : Symbol(T, Decl(inferFromNestedSameShapeTuple.ts, 37, 8))
function qq<U>(x: T1<U>, y: T2<U>) {
>qq : Symbol(qq, Decl(inferFromNestedSameShapeTuple.ts, 37, 32))
>U : Symbol(U, Decl(inferFromNestedSameShapeTuple.ts, 39, 12))
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 39, 15))
>T1 : Symbol(T1, Decl(inferFromNestedSameShapeTuple.ts, 32, 12))
>U : Symbol(U, Decl(inferFromNestedSameShapeTuple.ts, 39, 12))
>y : Symbol(y, Decl(inferFromNestedSameShapeTuple.ts, 39, 24))
>T2 : Symbol(T2, Decl(inferFromNestedSameShapeTuple.ts, 36, 36))
>U : Symbol(U, Decl(inferFromNestedSameShapeTuple.ts, 39, 12))
x = y;
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 39, 15))
>y : Symbol(y, Decl(inferFromNestedSameShapeTuple.ts, 39, 24))
y = x; // Error
>y : Symbol(y, Decl(inferFromNestedSameShapeTuple.ts, 39, 24))
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 39, 15))
}

View File

@ -84,3 +84,30 @@ const foo2 = getIds([{
}]
}] as const)
// Repro from comment in #49226
type T1<T> = [number, T1<{ x: T }>];
>T1 : T1<T>
>x : T
type T2<T> = [42, T2<{ x: T }>];
>T2 : T2<T>
>x : T
function qq<U>(x: T1<U>, y: T2<U>) {
>qq : <U>(x: T1<U>, y: T2<U>) => void
>x : T1<U>
>y : T2<U>
x = y;
>x = y : T2<U>
>x : T1<U>
>y : T2<U>
y = x; // Error
>y = x : T1<U>
>y : T2<U>
>x : T1<U>
}

View File

@ -146,7 +146,7 @@ tests/cases/compiler/recursiveConditionalTypes.ts(169,5): error TS2322: Type 'nu
declare let z: Box2<Box2<string>>;
foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)
foo(z); // string
// Intersect tuple element types

View File

@ -92,7 +92,7 @@ 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)
foo(z); // string
// Intersect tuple element types
@ -192,7 +192,7 @@ unbox(b3); // InfBox<string>
unbox({ value: { value: { value: { value: { value: { value: 5 } } } } } }); // number
unbox(b4); // { value: { value: typeof b4 }}
unbox({ value: { value: { get value() { return this; } } } }); // { readonly value: ... }
foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)
foo(z); // string
function f20(x, y) {
x = y;
y = x;

View File

@ -362,7 +362,7 @@ declare let z: Box2<Box2<string>>;
>Box2 : Symbol(Box2, Decl(recursiveConditionalTypes.ts, 86, 28))
>Box2 : Symbol(Box2, Decl(recursiveConditionalTypes.ts, 86, 28))
foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)
foo(z); // string
>foo : Symbol(foo, Decl(recursiveConditionalTypes.ts, 87, 28))
>z : Symbol(z, Decl(recursiveConditionalTypes.ts, 91, 11))

View File

@ -248,8 +248,8 @@ declare function foo<T>(x: Box1<Box1<T>>): T;
declare let z: Box2<Box2<string>>;
>z : Box2<Box2<string>>
foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)
>foo(z) : unknown
foo(z); // string
>foo(z) : string
>foo : <T>(x: Box1<Box1<T>>) => T
>z : Box2<Box2<string>>

View File

@ -33,4 +33,14 @@ const foo2 = getIds([{
id: 'b',
children: []
}]
}] as const)
}] as const)
// Repro from comment in #49226
type T1<T> = [number, T1<{ x: T }>];
type T2<T> = [42, T2<{ x: T }>];
function qq<U>(x: T1<U>, y: T2<U>) {
x = y;
y = x; // Error
}

View File

@ -95,7 +95,7 @@ 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)
foo(z); // string
// Intersect tuple element types