diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 65f550cb664..0a61d3aa177 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15486,8 +15486,7 @@ namespace ts { let visited: Map; let bivariant = false; let propagationType: Type; - let inferenceMatch = false; - let inferenceIncomplete = false; + let inferencePriority = InferencePriority.MaxValue; let allowComplexConstraintInference = true; inferFromTypes(originalSource, originalTarget); @@ -15600,7 +15599,7 @@ namespace ts { clearCachedInferences(inferences); } } - inferenceMatch = true; + inferencePriority = Math.min(inferencePriority, priority); return; } else { @@ -15694,19 +15693,15 @@ namespace ts { const key = source.id + "," + target.id; const status = visited && visited.get(key); if (status !== undefined) { - if (status & 1) inferenceMatch = true; - if (status & 2) inferenceIncomplete = true; + inferencePriority = Math.min(inferencePriority, status); return; } - (visited || (visited = createMap())).set(key, 0); - const saveInferenceMatch = inferenceMatch; - const saveInferenceIncomplete = inferenceIncomplete; - inferenceMatch = false; - inferenceIncomplete = false; + (visited || (visited = createMap())).set(key, -1); + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; action(source, target); - visited.set(key, (inferenceMatch ? 1 : 0) | (inferenceIncomplete ? 2 : 0)); - inferenceMatch = inferenceMatch || saveInferenceMatch; - inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete; + visited.set(key, inferencePriority); + inferencePriority = Math.min(inferencePriority, saveInferencePriority); } function inferFromMatchingType(source: Type, targets: Type[], matches: (s: Type, t: Type) => boolean) { @@ -15778,10 +15773,11 @@ namespace ts { let nakedTypeVariable: Type | undefined; const sources = source.flags & TypeFlags.Union ? (source).types : [source]; const matched = new Array(sources.length); - const saveInferenceIncomplete = inferenceIncomplete; - inferenceIncomplete = false; + let inferenceCircularity = false; // 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. + // track whether inferences were made from that particular type to some target with + // equal priority (i.e. of equal quality) to what we would infer for a naked type + // parameter. for (const t of targets) { if (getInferenceInfoForType(t)) { nakedTypeVariable = t; @@ -15789,20 +15785,20 @@ namespace ts { } else { for (let i = 0; i < sources.length; i++) { - const saveInferenceMatch = inferenceMatch; - inferenceMatch = false; + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; inferFromTypes(sources[i], t); - if (inferenceMatch) matched[i] = true; - inferenceMatch = inferenceMatch || saveInferenceMatch; + if (inferencePriority === priority) matched[i] = true; + inferenceCircularity = inferenceCircularity || inferencePriority < 0; + inferencePriority = Math.min(inferencePriority, saveInferencePriority); } } } - const inferenceComplete = !inferenceIncomplete; - inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete; - // If the target has a single naked type variable and inference completed (meaning we - // explored the types fully), create a union of the source types from which no inferences - // have been made so far and infer from that union to the naked type variable. - if (typeVariableCount === 1 && inferenceComplete) { + // If the target has a single naked type variable and no inference circularities were + // encountered above (meaning we explored the types fully), create a union of the source + // types from which no inferences have been made so far and infer from that union to the + // naked type variable. + if (typeVariableCount === 1 && !inferenceCircularity) { const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); if (unmatched.length) { inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); @@ -15905,7 +15901,7 @@ namespace ts { const symbol = isNonConstructorObject ? target.symbol : undefined; if (symbol) { if (contains(symbolStack, symbol)) { - inferenceIncomplete = true; + inferencePriority = -1; return; } (symbolStack || (symbolStack = [])).push(symbol); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b04cac47143..7d98b7761ac 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4471,6 +4471,7 @@ namespace ts { LiteralKeyof = 1 << 5, // Inference made from a string literal to a keyof T NoConstraints = 1 << 6, // Don't infer from constraints of instantiable types AlwaysStrict = 1 << 7, // Always use strict rules for contravariant inferences + MaxValue = 1 << 8, // Seed for inference priority tracking PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates }