From d8b191a671c9cddd095d52b31c06d89222e5dd77 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 16 Jul 2019 16:29:52 -0700 Subject: [PATCH] Improve algorithm for inferring to union types --- src/compiler/checker.ts | 51 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 06395d935b7..75b12ee37bb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15458,6 +15458,7 @@ namespace ts { let visited: Map; let bivariant = false; let propagationType: Type; + let inferenceCount = 0; let allowComplexConstraintInference = true; inferFromTypes(originalSource, originalTarget); @@ -15561,6 +15562,7 @@ namespace ts { clearCachedInferences(inferences); } } + inferenceCount++; return; } else { @@ -15610,13 +15612,16 @@ namespace ts { inferFromTypes(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target)); inferFromTypes(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target)); } + else if (target.flags & TypeFlags.Union) { + inferToUnionType(source, target); + } + else if (target.flags & TypeFlags.Intersection) { + inferToMultipleTypes(source, (target).types, /*isIntersection*/ true); + } else if (target.flags & TypeFlags.Conditional && !contravariant) { const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; inferToMultipleTypes(source, targetTypes, /*isIntersection*/ false); } - else if (target.flags & TypeFlags.UnionOrIntersection) { - inferToMultipleTypes(source, (target).types, !!(target.flags & TypeFlags.Intersection)); - } else if (source.flags & TypeFlags.Union) { // Source is a union or intersection type, infer from each constituent type const sourceTypes = (source).types; @@ -15742,6 +15747,46 @@ namespace ts { } } + function inferToUnionType(source: Type, target: UnionType) { + const sources = source.flags & TypeFlags.Union ? (source).types : [source]; + const matched = new Array(sources.length); + let typeVariableCount = 0; + // First infer to types that are not naked type variables. For each source type we + // track whether inferences were made from that particular type to some target. + for (const t of target.types) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; + } + else { + for (let i = 0; i < sources.length; i++) { + const count = inferenceCount; + inferFromTypes(sources[i], t); + if (count !== inferenceCount) matched[i] = true; + } + } + } + // If there are naked type variables in the target, create a union of the source types + // from which no inferences have been made so far and infer from that union to each naked + // type variable. If there is more than one naked type variable, give lower priority to + // the inferences as they are less specific. + if (typeVariableCount > 0) { + const unmatched = flatMap(sources, (s, i) => matched![i] ? undefined : s); + if (unmatched.length) { + const s = getUnionType(unmatched); + const savePriority = priority; + if (typeVariableCount > 1) { + priority |= InferencePriority.NakedTypeVariable; + } + for (const t of target.types) { + if (getInferenceInfoForType(t)) { + inferFromTypes(s, t); + } + } + priority = savePriority; + } + } + } + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { if (constraintType.flags & TypeFlags.Union) { let result = false;