Skip to content

Commit e15a9fb

Browse files
authored
Track tuple type recursion in inferFromObjectTypes (#37479)
* Track recursive tuple types in inferFromObjectTypes * Add regression test
1 parent 7e07a2b commit e15a9fb

File tree

6 files changed

+209
-6
lines changed

6 files changed

+209
-6
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18131,7 +18131,7 @@ namespace ts {
1813118131
}
1813218132

1813318133
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) {
18134-
let symbolStack: Symbol[];
18134+
let symbolOrTypeStack: (Symbol | Type)[];
1813518135
let visited: Map<number>;
1813618136
let bivariant = false;
1813718137
let propagationType: Type;
@@ -18570,15 +18570,15 @@ namespace ts {
1857018570
// its symbol with the instance side which would lead to false positives.
1857118571
const isNonConstructorObject = target.flags & TypeFlags.Object &&
1857218572
!(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class);
18573-
const symbol = isNonConstructorObject ? target.symbol : undefined;
18574-
if (symbol) {
18575-
if (contains(symbolStack, symbol)) {
18573+
const symbolOrType = isNonConstructorObject ? isTupleType(target) ? target.target : target.symbol : undefined;
18574+
if (symbolOrType) {
18575+
if (contains(symbolOrTypeStack, symbolOrType)) {
1857618576
inferencePriority = InferencePriority.Circularity;
1857718577
return;
1857818578
}
18579-
(symbolStack || (symbolStack = [])).push(symbol);
18579+
(symbolOrTypeStack || (symbolOrTypeStack = [])).push(symbolOrType);
1858018580
inferFromObjectTypesWorker(source, target);
18581-
symbolStack.pop();
18581+
symbolOrTypeStack.pop();
1858218582
}
1858318583
else {
1858418584
inferFromObjectTypesWorker(source, target);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
tests/cases/compiler/recursiveTupleTypeInference.ts(23,5): error TS2345: Argument of type '{ b: A; }' is not assignable to parameter of type 'G<{ b: unknown; }>'.
2+
Types of property 'b' are incompatible.
3+
Type 'A' is not assignable to type '[[[[[[[[[[[[any, "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"]'.
4+
Type '"number"' is not assignable to type '[[[[[[[[[[[[any, "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"]'.
5+
6+
7+
==== tests/cases/compiler/recursiveTupleTypeInference.ts (1 errors) ====
8+
// Repro from #37475
9+
10+
export type A = "number" | "null" | A[];
11+
12+
export type F<T> = null extends T
13+
? [F<NonNullable<T>>, "null"]
14+
: T extends number
15+
? "number"
16+
: never;
17+
18+
export type G<T> = { [k in keyof T]: F<T[k]> };
19+
20+
interface K {
21+
b: number | null;
22+
}
23+
24+
const gK: { [key in keyof K]: A } = { b: ["number", "null"] };
25+
26+
function foo<T>(g: G<T>): T {
27+
return {} as any;
28+
}
29+
30+
foo(gK);
31+
~~
32+
!!! error TS2345: Argument of type '{ b: A; }' is not assignable to parameter of type 'G<{ b: unknown; }>'.
33+
!!! error TS2345: Types of property 'b' are incompatible.
34+
!!! error TS2345: Type 'A' is not assignable to type '[[[[[[[[[[[[any, "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"]'.
35+
!!! error TS2345: Type '"number"' is not assignable to type '[[[[[[[[[[[[any, "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"], "null"]'.
36+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [recursiveTupleTypeInference.ts]
2+
// Repro from #37475
3+
4+
export type A = "number" | "null" | A[];
5+
6+
export type F<T> = null extends T
7+
? [F<NonNullable<T>>, "null"]
8+
: T extends number
9+
? "number"
10+
: never;
11+
12+
export type G<T> = { [k in keyof T]: F<T[k]> };
13+
14+
interface K {
15+
b: number | null;
16+
}
17+
18+
const gK: { [key in keyof K]: A } = { b: ["number", "null"] };
19+
20+
function foo<T>(g: G<T>): T {
21+
return {} as any;
22+
}
23+
24+
foo(gK);
25+
26+
27+
//// [recursiveTupleTypeInference.js]
28+
"use strict";
29+
// Repro from #37475
30+
exports.__esModule = true;
31+
var gK = { b: ["number", "null"] };
32+
function foo(g) {
33+
return {};
34+
}
35+
foo(gK);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
=== tests/cases/compiler/recursiveTupleTypeInference.ts ===
2+
// Repro from #37475
3+
4+
export type A = "number" | "null" | A[];
5+
>A : Symbol(A, Decl(recursiveTupleTypeInference.ts, 0, 0))
6+
>A : Symbol(A, Decl(recursiveTupleTypeInference.ts, 0, 0))
7+
8+
export type F<T> = null extends T
9+
>F : Symbol(F, Decl(recursiveTupleTypeInference.ts, 2, 40))
10+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 4, 14))
11+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 4, 14))
12+
13+
? [F<NonNullable<T>>, "null"]
14+
>F : Symbol(F, Decl(recursiveTupleTypeInference.ts, 2, 40))
15+
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
16+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 4, 14))
17+
18+
: T extends number
19+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 4, 14))
20+
21+
? "number"
22+
: never;
23+
24+
export type G<T> = { [k in keyof T]: F<T[k]> };
25+
>G : Symbol(G, Decl(recursiveTupleTypeInference.ts, 8, 12))
26+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 10, 14))
27+
>k : Symbol(k, Decl(recursiveTupleTypeInference.ts, 10, 22))
28+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 10, 14))
29+
>F : Symbol(F, Decl(recursiveTupleTypeInference.ts, 2, 40))
30+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 10, 14))
31+
>k : Symbol(k, Decl(recursiveTupleTypeInference.ts, 10, 22))
32+
33+
interface K {
34+
>K : Symbol(K, Decl(recursiveTupleTypeInference.ts, 10, 47))
35+
36+
b: number | null;
37+
>b : Symbol(K.b, Decl(recursiveTupleTypeInference.ts, 12, 13))
38+
}
39+
40+
const gK: { [key in keyof K]: A } = { b: ["number", "null"] };
41+
>gK : Symbol(gK, Decl(recursiveTupleTypeInference.ts, 16, 5))
42+
>key : Symbol(key, Decl(recursiveTupleTypeInference.ts, 16, 13))
43+
>K : Symbol(K, Decl(recursiveTupleTypeInference.ts, 10, 47))
44+
>A : Symbol(A, Decl(recursiveTupleTypeInference.ts, 0, 0))
45+
>b : Symbol(b, Decl(recursiveTupleTypeInference.ts, 16, 37))
46+
47+
function foo<T>(g: G<T>): T {
48+
>foo : Symbol(foo, Decl(recursiveTupleTypeInference.ts, 16, 62))
49+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 18, 13))
50+
>g : Symbol(g, Decl(recursiveTupleTypeInference.ts, 18, 16))
51+
>G : Symbol(G, Decl(recursiveTupleTypeInference.ts, 8, 12))
52+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 18, 13))
53+
>T : Symbol(T, Decl(recursiveTupleTypeInference.ts, 18, 13))
54+
55+
return {} as any;
56+
}
57+
58+
foo(gK);
59+
>foo : Symbol(foo, Decl(recursiveTupleTypeInference.ts, 16, 62))
60+
>gK : Symbol(gK, Decl(recursiveTupleTypeInference.ts, 16, 5))
61+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
=== tests/cases/compiler/recursiveTupleTypeInference.ts ===
2+
// Repro from #37475
3+
4+
export type A = "number" | "null" | A[];
5+
>A : A
6+
7+
export type F<T> = null extends T
8+
>F : F<T>
9+
>null : null
10+
11+
? [F<NonNullable<T>>, "null"]
12+
: T extends number
13+
? "number"
14+
: never;
15+
16+
export type G<T> = { [k in keyof T]: F<T[k]> };
17+
>G : G<T>
18+
19+
interface K {
20+
b: number | null;
21+
>b : number | null
22+
>null : null
23+
}
24+
25+
const gK: { [key in keyof K]: A } = { b: ["number", "null"] };
26+
>gK : { b: A; }
27+
>{ b: ["number", "null"] } : { b: ("number" | "null")[]; }
28+
>b : ("number" | "null")[]
29+
>["number", "null"] : ("number" | "null")[]
30+
>"number" : "number"
31+
>"null" : "null"
32+
33+
function foo<T>(g: G<T>): T {
34+
>foo : <T>(g: G<T>) => T
35+
>g : G<T>
36+
37+
return {} as any;
38+
>{} as any : any
39+
>{} : {}
40+
}
41+
42+
foo(gK);
43+
>foo(gK) : { b: unknown; }
44+
>foo : <T>(g: G<T>) => T
45+
>gK : { b: A; }
46+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// @strict: true
2+
3+
// Repro from #37475
4+
5+
export type A = "number" | "null" | A[];
6+
7+
export type F<T> = null extends T
8+
? [F<NonNullable<T>>, "null"]
9+
: T extends number
10+
? "number"
11+
: never;
12+
13+
export type G<T> = { [k in keyof T]: F<T[k]> };
14+
15+
interface K {
16+
b: number | null;
17+
}
18+
19+
const gK: { [key in keyof K]: A } = { b: ["number", "null"] };
20+
21+
function foo<T>(g: G<T>): T {
22+
return {} as any;
23+
}
24+
25+
foo(gK);

0 commit comments

Comments
 (0)