diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0b8ba29a710..40385062b1b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11118,7 +11118,7 @@ namespace ts { * property is computed by inferring from the source property type to X for the type * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). */ - function inferTypeForHomomorphicMappedType(source: Type, target: MappedType): Type { + function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, mappedTypeStack: string[]): Type { const properties = getPropertiesOfType(source); let indexInfo = getIndexInfoOfType(source, IndexKind.String); if (properties.length === 0 && !indexInfo) { @@ -11151,7 +11151,7 @@ namespace ts { function inferTargetType(sourceType: Type): Type { inference.candidates = undefined; - inferTypes(inferences, sourceType, templateType); + inferTypes(inferences, sourceType, templateType, 0, mappedTypeStack); return inference.candidates ? getUnionType(inference.candidates, /*subtypeReduction*/ true) : emptyObjectType; } } @@ -11169,7 +11169,7 @@ namespace ts { return undefined; } - function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) { + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, mappedTypeStack?: string[]) { let symbolStack: Symbol[]; let visited: Map; inferFromTypes(originalSource, originalTarget); @@ -11386,7 +11386,13 @@ namespace ts { // such that direct inferences to T get priority over inferences to Partial, for example. const inference = getInferenceInfoForType((constraintType).type); if (inference && !inference.isFixed) { - const inferredType = inferTypeForHomomorphicMappedType(source, target); + const key = (source.symbol ? getSymbolId(source.symbol) + "," : "") + getSymbolId(target.symbol); + if (contains(mappedTypeStack, key)) { + return; + } + (mappedTypeStack || (mappedTypeStack = [])).push(key); + const inferredType = inferTypeForHomomorphicMappedType(source, target, mappedTypeStack); + mappedTypeStack.pop(); if (inferredType) { const savePriority = priority; priority |= InferencePriority.MappedType; diff --git a/tests/baselines/reference/mappedTypeRecursiveInference.js b/tests/baselines/reference/mappedTypeRecursiveInference.js new file mode 100644 index 00000000000..5b7631818bc --- /dev/null +++ b/tests/baselines/reference/mappedTypeRecursiveInference.js @@ -0,0 +1,10 @@ +//// [mappedTypeRecursiveInference.ts] +interface A { a: A } +declare let a: A; +type Deep = { [K in keyof T]: Deep } +declare function foo(deep: Deep): T; +const out = foo(a); + + +//// [mappedTypeRecursiveInference.js] +var out = foo(a); diff --git a/tests/baselines/reference/mappedTypeRecursiveInference.symbols b/tests/baselines/reference/mappedTypeRecursiveInference.symbols new file mode 100644 index 00000000000..a3fa3722551 --- /dev/null +++ b/tests/baselines/reference/mappedTypeRecursiveInference.symbols @@ -0,0 +1,32 @@ +=== tests/cases/compiler/mappedTypeRecursiveInference.ts === +interface A { a: A } +>A : Symbol(A, Decl(mappedTypeRecursiveInference.ts, 0, 0)) +>a : Symbol(A.a, Decl(mappedTypeRecursiveInference.ts, 0, 13)) +>A : Symbol(A, Decl(mappedTypeRecursiveInference.ts, 0, 0)) + +declare let a: A; +>a : Symbol(a, Decl(mappedTypeRecursiveInference.ts, 1, 11)) +>A : Symbol(A, Decl(mappedTypeRecursiveInference.ts, 0, 0)) + +type Deep = { [K in keyof T]: Deep } +>Deep : Symbol(Deep, Decl(mappedTypeRecursiveInference.ts, 1, 17)) +>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 2, 10)) +>K : Symbol(K, Decl(mappedTypeRecursiveInference.ts, 2, 18)) +>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 2, 10)) +>Deep : Symbol(Deep, Decl(mappedTypeRecursiveInference.ts, 1, 17)) +>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 2, 10)) +>K : Symbol(K, Decl(mappedTypeRecursiveInference.ts, 2, 18)) + +declare function foo(deep: Deep): T; +>foo : Symbol(foo, Decl(mappedTypeRecursiveInference.ts, 2, 45)) +>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 3, 21)) +>deep : Symbol(deep, Decl(mappedTypeRecursiveInference.ts, 3, 24)) +>Deep : Symbol(Deep, Decl(mappedTypeRecursiveInference.ts, 1, 17)) +>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 3, 21)) +>T : Symbol(T, Decl(mappedTypeRecursiveInference.ts, 3, 21)) + +const out = foo(a); +>out : Symbol(out, Decl(mappedTypeRecursiveInference.ts, 4, 5)) +>foo : Symbol(foo, Decl(mappedTypeRecursiveInference.ts, 2, 45)) +>a : Symbol(a, Decl(mappedTypeRecursiveInference.ts, 1, 11)) + diff --git a/tests/baselines/reference/mappedTypeRecursiveInference.types b/tests/baselines/reference/mappedTypeRecursiveInference.types new file mode 100644 index 00000000000..382135591d5 --- /dev/null +++ b/tests/baselines/reference/mappedTypeRecursiveInference.types @@ -0,0 +1,33 @@ +=== tests/cases/compiler/mappedTypeRecursiveInference.ts === +interface A { a: A } +>A : A +>a : A +>A : A + +declare let a: A; +>a : A +>A : A + +type Deep = { [K in keyof T]: Deep } +>Deep : Deep +>T : T +>K : K +>T : T +>Deep : Deep +>T : T +>K : K + +declare function foo(deep: Deep): T; +>foo : (deep: Deep) => T +>T : T +>deep : Deep +>Deep : Deep +>T : T +>T : T + +const out = foo(a); +>out : { a: {}; } +>foo(a) : { a: {}; } +>foo : (deep: Deep) => T +>a : A + diff --git a/tests/cases/compiler/mappedTypeRecursiveInference.ts b/tests/cases/compiler/mappedTypeRecursiveInference.ts new file mode 100644 index 00000000000..d7a9cf3a0be --- /dev/null +++ b/tests/cases/compiler/mappedTypeRecursiveInference.ts @@ -0,0 +1,5 @@ +interface A { a: A } +declare let a: A; +type Deep = { [K in keyof T]: Deep } +declare function foo(deep: Deep): T; +const out = foo(a);