Skip to content

Commit 8d7c2a2

Browse files
authored
Merge pull request microsoft#20370 from Microsoft/cutoff-inference-for-recursive-mapped-types
Cut off inference for recursive mapped types
2 parents bc628bf + 84d747a commit 8d7c2a2

5 files changed

Lines changed: 90 additions & 4 deletions

File tree

src/compiler/checker.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11118,7 +11118,7 @@ namespace ts {
1111811118
* property is computed by inferring from the source property type to X for the type
1111911119
* variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for).
1112011120
*/
11121-
function inferTypeForHomomorphicMappedType(source: Type, target: MappedType): Type {
11121+
function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, mappedTypeStack: string[]): Type {
1112211122
const properties = getPropertiesOfType(source);
1112311123
let indexInfo = getIndexInfoOfType(source, IndexKind.String);
1112411124
if (properties.length === 0 && !indexInfo) {
@@ -11151,7 +11151,7 @@ namespace ts {
1115111151

1115211152
function inferTargetType(sourceType: Type): Type {
1115311153
inference.candidates = undefined;
11154-
inferTypes(inferences, sourceType, templateType);
11154+
inferTypes(inferences, sourceType, templateType, 0, mappedTypeStack);
1115511155
return inference.candidates ? getUnionType(inference.candidates, /*subtypeReduction*/ true) : emptyObjectType;
1115611156
}
1115711157
}
@@ -11169,7 +11169,7 @@ namespace ts {
1116911169
return undefined;
1117011170
}
1117111171

11172-
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) {
11172+
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, mappedTypeStack?: string[]) {
1117311173
let symbolStack: Symbol[];
1117411174
let visited: Map<boolean>;
1117511175
inferFromTypes(originalSource, originalTarget);
@@ -11386,7 +11386,13 @@ namespace ts {
1138611386
// such that direct inferences to T get priority over inferences to Partial<T>, for example.
1138711387
const inference = getInferenceInfoForType((<IndexType>constraintType).type);
1138811388
if (inference && !inference.isFixed) {
11389-
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target);
11389+
const key = (source.symbol ? getSymbolId(source.symbol) + "," : "") + getSymbolId(target.symbol);
11390+
if (contains(mappedTypeStack, key)) {
11391+
return;
11392+
}
11393+
(mappedTypeStack || (mappedTypeStack = [])).push(key);
11394+
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target, mappedTypeStack);
11395+
mappedTypeStack.pop();
1139011396
if (inferredType) {
1139111397
const savePriority = priority;
1139211398
priority |= InferencePriority.MappedType;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//// [mappedTypeRecursiveInference.ts]
2+
interface A { a: A }
3+
declare let a: A;
4+
type Deep<T> = { [K in keyof T]: Deep<T[K]> }
5+
declare function foo<T>(deep: Deep<T>): T;
6+
const out = foo(a);
7+
8+
9+
//// [mappedTypeRecursiveInference.js]
10+
var out = foo(a);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/compiler/mappedTypeRecursiveInference.ts ===
2+
interface A { a: A }
3+
>A : Symbol(A, Decl(mappedTypeRecursiveInference.ts, 0, 0))
4+
>a : Symbol(A.a, Decl(mappedTypeRecursiveInference.ts, 0, 13))
5+
>A : Symbol(A, Decl(mappedTypeRecursiveInference.ts, 0, 0))
6+
7+
declare let a: A;
8+
>a : Symbol(a, Decl(mappedTypeRecursiveInference.ts, 1, 11))
9+
>A : Symbol(A, Decl(mappedTypeRecursiveInference.ts, 0, 0))
10+
11+
type Deep<T> = { [K in keyof T]: Deep<T[K]> }
12+
>Deep : Symbol(Deep, Decl(mappedTypeRecursiveInference.ts, 1, 17))
13+
>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 2, 10))
14+
>K : Symbol(K, Decl(mappedTypeRecursiveInference.ts, 2, 18))
15+
>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 2, 10))
16+
>Deep : Symbol(Deep, Decl(mappedTypeRecursiveInference.ts, 1, 17))
17+
>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 2, 10))
18+
>K : Symbol(K, Decl(mappedTypeRecursiveInference.ts, 2, 18))
19+
20+
declare function foo<T>(deep: Deep<T>): T;
21+
>foo : Symbol(foo, Decl(mappedTypeRecursiveInference.ts, 2, 45))
22+
>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 3, 21))
23+
>deep : Symbol(deep, Decl(mappedTypeRecursiveInference.ts, 3, 24))
24+
>Deep : Symbol(Deep, Decl(mappedTypeRecursiveInference.ts, 1, 17))
25+
>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 3, 21))
26+
>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 3, 21))
27+
28+
const out = foo(a);
29+
>out : Symbol(out, Decl(mappedTypeRecursiveInference.ts, 4, 5))
30+
>foo : Symbol(foo, Decl(mappedTypeRecursiveInference.ts, 2, 45))
31+
>a : Symbol(a, Decl(mappedTypeRecursiveInference.ts, 1, 11))
32+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/compiler/mappedTypeRecursiveInference.ts ===
2+
interface A { a: A }
3+
>A : A
4+
>a : A
5+
>A : A
6+
7+
declare let a: A;
8+
>a : A
9+
>A : A
10+
11+
type Deep<T> = { [K in keyof T]: Deep<T[K]> }
12+
>Deep : Deep<T>
13+
>T : T
14+
>K : K
15+
>T : T
16+
>Deep : Deep<T>
17+
>T : T
18+
>K : K
19+
20+
declare function foo<T>(deep: Deep<T>): T;
21+
>foo : <T>(deep: Deep<T>) => T
22+
>T : T
23+
>deep : Deep<T>
24+
>Deep : Deep<T>
25+
>T : T
26+
>T : T
27+
28+
const out = foo(a);
29+
>out : { a: {}; }
30+
>foo(a) : { a: {}; }
31+
>foo : <T>(deep: Deep<T>) => T
32+
>a : A
33+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
interface A { a: A }
2+
declare let a: A;
3+
type Deep<T> = { [K in keyof T]: Deep<T[K]> }
4+
declare function foo<T>(deep: Deep<T>): T;
5+
const out = foo(a);

0 commit comments

Comments
 (0)