From b1316e589e101cc6a438dcbd9a70224d97211bde Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 30 Nov 2017 09:33:05 -0800 Subject: [PATCH] Cut off inference for recursive mapped types Previously, when inferring to a self-referential (or otherwise recursive) homomorphic mapped type from a source type that also has recursive references, type inference would enter infinite recursion. Now there is a more complex stack for mapped type inference. It mirrors the existing symbolStack but (1) includes the source type and (2) is passed through inferTypeForHomomorphicMappedType, which is actually called outside of inferTypes, and so restarts the symbolStack cache every time. --- src/compiler/checker.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cd4eeed7522..8fa124868cc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11127,7 +11127,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: [Type, Symbol][]): Type { const properties = getPropertiesOfType(source); let indexInfo = getIndexInfoOfType(source, IndexKind.String); if (properties.length === 0 && !indexInfo) { @@ -11160,7 +11160,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; } } @@ -11178,7 +11178,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?: [Type, Symbol][]) { let symbolStack: Symbol[]; let visited: Map; inferFromTypes(originalSource, originalTarget); @@ -11395,7 +11395,12 @@ 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); + if (contains(mappedTypeStack, [source, target.symbol], ([s1,t1],[s2,t2]) => s1 === s2 && t1 === t2)) { + return; + } + (mappedTypeStack || (mappedTypeStack = [])).push([source, target.symbol]); + const inferredType = inferTypeForHomomorphicMappedType(source, target, mappedTypeStack); + mappedTypeStack.pop(); if (inferredType) { const savePriority = priority; priority |= InferencePriority.MappedType;