Skip to content

Commit 58b71fe

Browse files
committed
Allow inference to explore multiple instances of the same symbol
1 parent 63b8c64 commit 58b71fe

File tree

7 files changed

+139
-52
lines changed

7 files changed

+139
-52
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace ts {
77
let nextMergeId = 1;
88
let nextFlowId = 1;
99

10+
const maxSymbolRecusionDepth = 10;
11+
1012
export function getNodeId(node: Node): number {
1113
if (!node.id) {
1214
node.id = nextNodeId;
@@ -3722,7 +3724,7 @@ namespace ts {
37223724
}
37233725

37243726
const depth = context.symbolDepth.get(id) || 0;
3725-
if (depth > 10) {
3727+
if (depth > maxSymbolRecusionDepth) {
37263728
return createElidedInformationPlaceholder(context);
37273729
}
37283730
context.symbolDepth.set(id, depth + 1);
@@ -15139,7 +15141,7 @@ namespace ts {
1513915141
}
1514015142

1514115143
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) {
15142-
let symbolStack: Symbol[];
15144+
let symbolDepth: Map<number>;
1514315145
let visited: Map<boolean>;
1514415146
let bivariant = false;
1514515147
let propagationType: Type;
@@ -15150,6 +15152,12 @@ namespace ts {
1515015152
if (!couldContainTypeVariables(target)) {
1515115153
return;
1515215154
}
15155+
15156+
const key = source.id + "," + target.id;
15157+
if (visited && visited.get(key)) {
15158+
return;
15159+
}
15160+
(visited || (visited = createMap<boolean>())).set(key, true);
1515315161
if (source === wildcardType) {
1515415162
// We are inferring from an 'any' type. We want to infer this type for every type parameter
1515515163
// referenced in the target type, so we record it as the propagation type and infer from the
@@ -15256,7 +15264,7 @@ namespace ts {
1525615264
// Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
1525715265
const simplified = getSimplifiedType(target, /*writing*/ false);
1525815266
if (simplified !== target) {
15259-
inferFromTypesOnce(source, simplified);
15267+
inferFromTypes(source, simplified);
1526015268
}
1526115269
else if (target.flags & TypeFlags.IndexedAccess) {
1526215270
const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false);
@@ -15265,7 +15273,7 @@ namespace ts {
1526515273
if (indexType.flags & TypeFlags.Instantiable) {
1526615274
const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false);
1526715275
if (simplified && simplified !== target) {
15268-
inferFromTypesOnce(source, simplified);
15276+
inferFromTypes(source, simplified);
1526915277
}
1527015278
}
1527115279
}
@@ -15369,11 +15377,6 @@ namespace ts {
1536915377
source = apparentSource;
1537015378
}
1537115379
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
15372-
const key = source.id + "," + target.id;
15373-
if (visited && visited.get(key)) {
15374-
return;
15375-
}
15376-
(visited || (visited = createMap<boolean>())).set(key, true);
1537715380
// If we are already processing another target type with the same associated symbol (such as
1537815381
// an instantiation of the same generic type), we do not explore this target as it would yield
1537915382
// no further inferences. We exclude the static side of classes from this check since it shares
@@ -15382,26 +15385,20 @@ namespace ts {
1538215385
!(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class);
1538315386
const symbol = isNonConstructorObject ? target.symbol : undefined;
1538415387
if (symbol) {
15385-
if (contains(symbolStack, symbol)) {
15388+
const id = "" + getSymbolId(symbol);
15389+
const depth = symbolDepth && symbolDepth.get(id) || 0;
15390+
if (depth > maxSymbolRecusionDepth) {
1538615391
return;
1538715392
}
15388-
(symbolStack || (symbolStack = [])).push(symbol);
15393+
(symbolDepth || (symbolDepth = createMap())).set(id, depth + 1);
1538915394
inferFromObjectTypes(source, target);
15390-
symbolStack.pop();
15395+
symbolDepth.set(id, depth);
1539115396
}
1539215397
else {
1539315398
inferFromObjectTypes(source, target);
1539415399
}
1539515400
}
1539615401
}
15397-
15398-
function inferFromTypesOnce(source: Type, target: Type) {
15399-
const key = source.id + "," + target.id;
15400-
if (!visited || !visited.get(key)) {
15401-
(visited || (visited = createMap<boolean>())).set(key, true);
15402-
inferFromTypes(source, target);
15403-
}
15404-
}
1540515402
}
1540615403

1540715404
function inferFromContravariantTypes(source: Type, target: Type) {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [mappedTypeAliasSubstitutability.ts]
2+
// repro from https://github.com/microsoft/TypeScript/issues/31616
3+
4+
const v = { test: { smth: 5 } };
5+
type Field<A extends string, R> = { [K in A]: R };
6+
const f = <A extends string, B extends string, R>(x: { [K in A]: Field<B, R> } ): R => ({} as any);
7+
const g = <A extends string, B extends string, R>(x: Field<A, Field<B, R>>): R => ({} as any);
8+
const r1 = f(v); // number
9+
const r2 = g(v); // unknown
10+
11+
12+
//// [mappedTypeAliasSubstitutability.js]
13+
// repro from https://github.com/microsoft/TypeScript/issues/31616
14+
var v = { test: { smth: 5 } };
15+
var f = function (x) { return ({}); };
16+
var g = function (x) { return ({}); };
17+
var r1 = f(v); // number
18+
var r2 = g(v); // unknown
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=== tests/cases/compiler/mappedTypeAliasSubstitutability.ts ===
2+
// repro from https://github.com/microsoft/TypeScript/issues/31616
3+
4+
const v = { test: { smth: 5 } };
5+
>v : Symbol(v, Decl(mappedTypeAliasSubstitutability.ts, 2, 5))
6+
>test : Symbol(test, Decl(mappedTypeAliasSubstitutability.ts, 2, 11))
7+
>smth : Symbol(smth, Decl(mappedTypeAliasSubstitutability.ts, 2, 19))
8+
9+
type Field<A extends string, R> = { [K in A]: R };
10+
>Field : Symbol(Field, Decl(mappedTypeAliasSubstitutability.ts, 2, 32))
11+
>A : Symbol(A, Decl(mappedTypeAliasSubstitutability.ts, 3, 11))
12+
>R : Symbol(R, Decl(mappedTypeAliasSubstitutability.ts, 3, 28))
13+
>K : Symbol(K, Decl(mappedTypeAliasSubstitutability.ts, 3, 37))
14+
>A : Symbol(A, Decl(mappedTypeAliasSubstitutability.ts, 3, 11))
15+
>R : Symbol(R, Decl(mappedTypeAliasSubstitutability.ts, 3, 28))
16+
17+
const f = <A extends string, B extends string, R>(x: { [K in A]: Field<B, R> } ): R => ({} as any);
18+
>f : Symbol(f, Decl(mappedTypeAliasSubstitutability.ts, 4, 5))
19+
>A : Symbol(A, Decl(mappedTypeAliasSubstitutability.ts, 4, 11))
20+
>B : Symbol(B, Decl(mappedTypeAliasSubstitutability.ts, 4, 28))
21+
>R : Symbol(R, Decl(mappedTypeAliasSubstitutability.ts, 4, 46))
22+
>x : Symbol(x, Decl(mappedTypeAliasSubstitutability.ts, 4, 50))
23+
>K : Symbol(K, Decl(mappedTypeAliasSubstitutability.ts, 4, 56))
24+
>A : Symbol(A, Decl(mappedTypeAliasSubstitutability.ts, 4, 11))
25+
>Field : Symbol(Field, Decl(mappedTypeAliasSubstitutability.ts, 2, 32))
26+
>B : Symbol(B, Decl(mappedTypeAliasSubstitutability.ts, 4, 28))
27+
>R : Symbol(R, Decl(mappedTypeAliasSubstitutability.ts, 4, 46))
28+
>R : Symbol(R, Decl(mappedTypeAliasSubstitutability.ts, 4, 46))
29+
30+
const g = <A extends string, B extends string, R>(x: Field<A, Field<B, R>>): R => ({} as any);
31+
>g : Symbol(g, Decl(mappedTypeAliasSubstitutability.ts, 5, 5))
32+
>A : Symbol(A, Decl(mappedTypeAliasSubstitutability.ts, 5, 11))
33+
>B : Symbol(B, Decl(mappedTypeAliasSubstitutability.ts, 5, 28))
34+
>R : Symbol(R, Decl(mappedTypeAliasSubstitutability.ts, 5, 46))
35+
>x : Symbol(x, Decl(mappedTypeAliasSubstitutability.ts, 5, 50))
36+
>Field : Symbol(Field, Decl(mappedTypeAliasSubstitutability.ts, 2, 32))
37+
>A : Symbol(A, Decl(mappedTypeAliasSubstitutability.ts, 5, 11))
38+
>Field : Symbol(Field, Decl(mappedTypeAliasSubstitutability.ts, 2, 32))
39+
>B : Symbol(B, Decl(mappedTypeAliasSubstitutability.ts, 5, 28))
40+
>R : Symbol(R, Decl(mappedTypeAliasSubstitutability.ts, 5, 46))
41+
>R : Symbol(R, Decl(mappedTypeAliasSubstitutability.ts, 5, 46))
42+
43+
const r1 = f(v); // number
44+
>r1 : Symbol(r1, Decl(mappedTypeAliasSubstitutability.ts, 6, 5))
45+
>f : Symbol(f, Decl(mappedTypeAliasSubstitutability.ts, 4, 5))
46+
>v : Symbol(v, Decl(mappedTypeAliasSubstitutability.ts, 2, 5))
47+
48+
const r2 = g(v); // unknown
49+
>r2 : Symbol(r2, Decl(mappedTypeAliasSubstitutability.ts, 7, 5))
50+
>g : Symbol(g, Decl(mappedTypeAliasSubstitutability.ts, 5, 5))
51+
>v : Symbol(v, Decl(mappedTypeAliasSubstitutability.ts, 2, 5))
52+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/mappedTypeAliasSubstitutability.ts ===
2+
// repro from https://github.com/microsoft/TypeScript/issues/31616
3+
4+
const v = { test: { smth: 5 } };
5+
>v : { test: { smth: number; }; }
6+
>{ test: { smth: 5 } } : { test: { smth: number; }; }
7+
>test : { smth: number; }
8+
>{ smth: 5 } : { smth: number; }
9+
>smth : number
10+
>5 : 5
11+
12+
type Field<A extends string, R> = { [K in A]: R };
13+
>Field : Field<A, R>
14+
15+
const f = <A extends string, B extends string, R>(x: { [K in A]: Field<B, R> } ): R => ({} as any);
16+
>f : <A extends string, B extends string, R>(x: { [K in A]: Field<B, R>; }) => R
17+
><A extends string, B extends string, R>(x: { [K in A]: Field<B, R> } ): R => ({} as any) : <A extends string, B extends string, R>(x: { [K in A]: Field<B, R>; }) => R
18+
>x : { [K in A]: Field<B, R>; }
19+
>({} as any) : any
20+
>{} as any : any
21+
>{} : {}
22+
23+
const g = <A extends string, B extends string, R>(x: Field<A, Field<B, R>>): R => ({} as any);
24+
>g : <A extends string, B extends string, R>(x: Field<A, Field<B, R>>) => R
25+
><A extends string, B extends string, R>(x: Field<A, Field<B, R>>): R => ({} as any) : <A extends string, B extends string, R>(x: Field<A, Field<B, R>>) => R
26+
>x : Field<A, Field<B, R>>
27+
>({} as any) : any
28+
>{} as any : any
29+
>{} : {}
30+
31+
const r1 = f(v); // number
32+
>r1 : number
33+
>f(v) : number
34+
>f : <A extends string, B extends string, R>(x: { [K in A]: Field<B, R>; }) => R
35+
>v : { test: { smth: number; }; }
36+
37+
const r2 = g(v); // unknown
38+
>r2 : number
39+
>g(v) : number
40+
>g : <A extends string, B extends string, R>(x: Field<A, Field<B, R>>) => R
41+
>v : { test: { smth: number; }; }
42+

tests/baselines/reference/promiseTypeInference.errors.txt

Lines changed: 0 additions & 30 deletions
This file was deleted.

tests/baselines/reference/promiseTypeInference.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ declare function convert(s: string): IPromise<number>;
2222
>s : string
2323

2424
var $$x = load("something").then(s => convert(s));
25-
>$$x : any
26-
>load("something").then(s => convert(s)) : any
25+
>$$x : Promise<number>
26+
>load("something").then(s => convert(s)) : Promise<number>
2727
>load("something").then : { <TResult1 = string, TResult2 = never>(onfulfilled?: (value: string) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>): Promise<TResult1 | TResult2>; <U>(success?: (value: string) => Promise<U>): Promise<U>; }
2828
>load("something") : Promise<string>
2929
>load : (name: string) => Promise<string>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// repro from https://github.com/microsoft/TypeScript/issues/31616
2+
3+
const v = { test: { smth: 5 } };
4+
type Field<A extends string, R> = { [K in A]: R };
5+
const f = <A extends string, B extends string, R>(x: { [K in A]: Field<B, R> } ): R => ({} as any);
6+
const g = <A extends string, B extends string, R>(x: Field<A, Field<B, R>>): R => ({} as any);
7+
const r1 = f(v); // number
8+
const r2 = g(v); // unknown

0 commit comments

Comments
 (0)