Skip to content

Commit 3d2c344

Browse files
authored
Fix recursive type inference (#53396)
1 parent bace689 commit 3d2c344

10 files changed

+143
-19
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22911,7 +22911,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2291122911
return type.symbol;
2291222912
}
2291322913
if (isTupleType(type)) {
22914-
return type;
22914+
return type.target;
2291522915
}
2291622916
}
2291722917
if (type.flags & TypeFlags.TypeParameter) {
@@ -24273,8 +24273,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2427324273
let inferencePriority: number = InferencePriority.MaxValue;
2427424274
let allowComplexConstraintInference = true;
2427524275
let visited: Map<string, number>;
24276-
let sourceStack: object[];
24277-
let targetStack: object[];
24276+
let sourceStack: Type[];
24277+
let targetStack: Type[];
2427824278
let expandingFlags = ExpandingFlags.None;
2427924279
inferFromTypes(originalSource, originalTarget);
2428024280

@@ -24530,20 +24530,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2453024530
// We stop inferring and report a circularity if we encounter duplicate recursion identities on both
2453124531
// the source side and the target side.
2453224532
const saveExpandingFlags = expandingFlags;
24533-
const sourceIdentity = getRecursionIdentity(source);
24534-
const targetIdentity = getRecursionIdentity(target);
24535-
if (contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source;
24536-
if (contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target;
24533+
(sourceStack ??= []).push(source);
24534+
(targetStack ??= []).push(target);
24535+
if (isDeeplyNestedType(source, sourceStack, sourceStack.length, 2)) expandingFlags |= ExpandingFlags.Source;
24536+
if (isDeeplyNestedType(target, targetStack, targetStack.length, 2)) expandingFlags |= ExpandingFlags.Target;
2453724537
if (expandingFlags !== ExpandingFlags.Both) {
24538-
(sourceStack || (sourceStack = [])).push(sourceIdentity);
24539-
(targetStack || (targetStack = [])).push(targetIdentity);
2454024538
action(source, target);
24541-
targetStack.pop();
24542-
sourceStack.pop();
2454324539
}
2454424540
else {
2454524541
inferencePriority = InferencePriority.Circularity;
2454624542
}
24543+
targetStack.pop();
24544+
sourceStack.pop();
2454724545
expandingFlags = saveExpandingFlags;
2454824546
visited.set(key, inferencePriority);
2454924547
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
tests/cases/compiler/inferFromNestedSameShapeTuple.ts(42,5): error TS2322: Type 'T1<U>' is not assignable to type 'T2<U>'.
2+
Type at position 0 in source is not compatible with type at position 0 in target.
3+
Type 'number' is not assignable to type '42'.
4+
5+
6+
==== tests/cases/compiler/inferFromNestedSameShapeTuple.ts (1 errors) ====
7+
// repro #48524
8+
9+
type Magic<X> = X extends [[infer Y, ...infer _], ...infer __] ? Y : never;
10+
11+
type R = Magic<[[number]]>
12+
13+
// repro #52722
14+
15+
type Recursive<Id> = {
16+
id: Id
17+
children: readonly Recursive<Id>[]
18+
}
19+
20+
declare function getIds<Id>(items: readonly Recursive<Id>[]): Id[];
21+
22+
const items = [{
23+
id: 'a',
24+
children: [{
25+
id: 'b',
26+
children: []
27+
}]
28+
}] as const satisfies readonly Recursive<string>[]
29+
30+
const foo = getIds(items)
31+
32+
// variant with a fresh argument
33+
const foo2 = getIds([{
34+
id: 'a',
35+
children: [{
36+
id: 'b',
37+
children: []
38+
}]
39+
}] as const)
40+
41+
// Repro from comment in #49226
42+
43+
type T1<T> = [number, T1<{ x: T }>];
44+
type T2<T> = [42, T2<{ x: T }>];
45+
46+
function qq<U>(x: T1<U>, y: T2<U>) {
47+
x = y;
48+
y = x; // Error
49+
~
50+
!!! error TS2322: Type 'T1<U>' is not assignable to type 'T2<U>'.
51+
!!! error TS2322: Type at position 0 in source is not compatible with type at position 0 in target.
52+
!!! error TS2322: Type 'number' is not assignable to type '42'.
53+
}
54+

tests/baselines/reference/inferFromNestedSameShapeTuple.symbols

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,38 @@ const foo2 = getIds([{
8484
}] as const)
8585
>const : Symbol(const)
8686

87+
// Repro from comment in #49226
88+
89+
type T1<T> = [number, T1<{ x: T }>];
90+
>T1 : Symbol(T1, Decl(inferFromNestedSameShapeTuple.ts, 32, 12))
91+
>T : Symbol(T, Decl(inferFromNestedSameShapeTuple.ts, 36, 8))
92+
>T1 : Symbol(T1, Decl(inferFromNestedSameShapeTuple.ts, 32, 12))
93+
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 36, 26))
94+
>T : Symbol(T, Decl(inferFromNestedSameShapeTuple.ts, 36, 8))
95+
96+
type T2<T> = [42, T2<{ x: T }>];
97+
>T2 : Symbol(T2, Decl(inferFromNestedSameShapeTuple.ts, 36, 36))
98+
>T : Symbol(T, Decl(inferFromNestedSameShapeTuple.ts, 37, 8))
99+
>T2 : Symbol(T2, Decl(inferFromNestedSameShapeTuple.ts, 36, 36))
100+
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 37, 22))
101+
>T : Symbol(T, Decl(inferFromNestedSameShapeTuple.ts, 37, 8))
102+
103+
function qq<U>(x: T1<U>, y: T2<U>) {
104+
>qq : Symbol(qq, Decl(inferFromNestedSameShapeTuple.ts, 37, 32))
105+
>U : Symbol(U, Decl(inferFromNestedSameShapeTuple.ts, 39, 12))
106+
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 39, 15))
107+
>T1 : Symbol(T1, Decl(inferFromNestedSameShapeTuple.ts, 32, 12))
108+
>U : Symbol(U, Decl(inferFromNestedSameShapeTuple.ts, 39, 12))
109+
>y : Symbol(y, Decl(inferFromNestedSameShapeTuple.ts, 39, 24))
110+
>T2 : Symbol(T2, Decl(inferFromNestedSameShapeTuple.ts, 36, 36))
111+
>U : Symbol(U, Decl(inferFromNestedSameShapeTuple.ts, 39, 12))
112+
113+
x = y;
114+
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 39, 15))
115+
>y : Symbol(y, Decl(inferFromNestedSameShapeTuple.ts, 39, 24))
116+
117+
y = x; // Error
118+
>y : Symbol(y, Decl(inferFromNestedSameShapeTuple.ts, 39, 24))
119+
>x : Symbol(x, Decl(inferFromNestedSameShapeTuple.ts, 39, 15))
120+
}
121+

tests/baselines/reference/inferFromNestedSameShapeTuple.types

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,30 @@ const foo2 = getIds([{
8484

8585
}]
8686
}] as const)
87+
88+
// Repro from comment in #49226
89+
90+
type T1<T> = [number, T1<{ x: T }>];
91+
>T1 : T1<T>
92+
>x : T
93+
94+
type T2<T> = [42, T2<{ x: T }>];
95+
>T2 : T2<T>
96+
>x : T
97+
98+
function qq<U>(x: T1<U>, y: T2<U>) {
99+
>qq : <U>(x: T1<U>, y: T2<U>) => void
100+
>x : T1<U>
101+
>y : T2<U>
102+
103+
x = y;
104+
>x = y : T2<U>
105+
>x : T1<U>
106+
>y : T2<U>
107+
108+
y = x; // Error
109+
>y = x : T1<U>
110+
>y : T2<U>
111+
>x : T1<U>
112+
}
113+

tests/baselines/reference/recursiveConditionalTypes.errors.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ tests/cases/compiler/recursiveConditionalTypes.ts(169,5): error TS2322: Type 'nu
146146

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

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

151151
// Intersect tuple element types
152152

tests/baselines/reference/recursiveConditionalTypes.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ declare function foo<T>(x: Box1<Box1<T>>): T;
9292

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

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

9797
// Intersect tuple element types
9898

@@ -192,7 +192,7 @@ unbox(b3); // InfBox<string>
192192
unbox({ value: { value: { value: { value: { value: { value: 5 } } } } } }); // number
193193
unbox(b4); // { value: { value: typeof b4 }}
194194
unbox({ value: { value: { get value() { return this; } } } }); // { readonly value: ... }
195-
foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)
195+
foo(z); // string
196196
function f20(x, y) {
197197
x = y;
198198
y = x;

tests/baselines/reference/recursiveConditionalTypes.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ declare let z: Box2<Box2<string>>;
362362
>Box2 : Symbol(Box2, Decl(recursiveConditionalTypes.ts, 86, 28))
363363
>Box2 : Symbol(Box2, Decl(recursiveConditionalTypes.ts, 86, 28))
364364

365-
foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)
365+
foo(z); // string
366366
>foo : Symbol(foo, Decl(recursiveConditionalTypes.ts, 87, 28))
367367
>z : Symbol(z, Decl(recursiveConditionalTypes.ts, 91, 11))
368368

tests/baselines/reference/recursiveConditionalTypes.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ declare function foo<T>(x: Box1<Box1<T>>): T;
248248
declare let z: Box2<Box2<string>>;
249249
>z : Box2<Box2<string>>
250250

251-
foo(z); // unknown, but ideally would be string (requires unique recursion ID for each type reference)
252-
>foo(z) : unknown
251+
foo(z); // string
252+
>foo(z) : string
253253
>foo : <T>(x: Box1<Box1<T>>) => T
254254
>z : Box2<Box2<string>>
255255

tests/cases/compiler/inferFromNestedSameShapeTuple.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,14 @@ const foo2 = getIds([{
3333
id: 'b',
3434
children: []
3535
}]
36-
}] as const)
36+
}] as const)
37+
38+
// Repro from comment in #49226
39+
40+
type T1<T> = [number, T1<{ x: T }>];
41+
type T2<T> = [42, T2<{ x: T }>];
42+
43+
function qq<U>(x: T1<U>, y: T2<U>) {
44+
x = y;
45+
y = x; // Error
46+
}

tests/cases/compiler/recursiveConditionalTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ declare function foo<T>(x: Box1<Box1<T>>): T;
9595

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

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

100100
// Intersect tuple element types
101101

0 commit comments

Comments
 (0)