Skip to content

Commit a81ce06

Browse files
committed
Stricter criteria for eliminating types in unions during inference
1 parent 2a2866c commit a81ce06

File tree

2 files changed

+24
-27
lines changed

2 files changed

+24
-27
lines changed

src/compiler/checker.ts

+23-27
Original file line numberDiff line numberDiff line change
@@ -15486,8 +15486,7 @@ namespace ts {
1548615486
let visited: Map<number>;
1548715487
let bivariant = false;
1548815488
let propagationType: Type;
15489-
let inferenceMatch = false;
15490-
let inferenceIncomplete = false;
15489+
let inferencePriority = InferencePriority.MaxValue;
1549115490
let allowComplexConstraintInference = true;
1549215491
inferFromTypes(originalSource, originalTarget);
1549315492

@@ -15600,7 +15599,7 @@ namespace ts {
1560015599
clearCachedInferences(inferences);
1560115600
}
1560215601
}
15603-
inferenceMatch = true;
15602+
inferencePriority = Math.min(inferencePriority, priority);
1560415603
return;
1560515604
}
1560615605
else {
@@ -15694,19 +15693,15 @@ namespace ts {
1569415693
const key = source.id + "," + target.id;
1569515694
const status = visited && visited.get(key);
1569615695
if (status !== undefined) {
15697-
if (status & 1) inferenceMatch = true;
15698-
if (status & 2) inferenceIncomplete = true;
15696+
inferencePriority = Math.min(inferencePriority, status);
1569915697
return;
1570015698
}
15701-
(visited || (visited = createMap<number>())).set(key, 0);
15702-
const saveInferenceMatch = inferenceMatch;
15703-
const saveInferenceIncomplete = inferenceIncomplete;
15704-
inferenceMatch = false;
15705-
inferenceIncomplete = false;
15699+
(visited || (visited = createMap<number>())).set(key, -1);
15700+
const saveInferencePriority = inferencePriority;
15701+
inferencePriority = InferencePriority.MaxValue;
1570615702
action(source, target);
15707-
visited.set(key, (inferenceMatch ? 1 : 0) | (inferenceIncomplete ? 2 : 0));
15708-
inferenceMatch = inferenceMatch || saveInferenceMatch;
15709-
inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete;
15703+
visited.set(key, inferencePriority);
15704+
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
1571015705
}
1571115706

1571215707
function inferFromMatchingType(source: Type, targets: Type[], matches: (s: Type, t: Type) => boolean) {
@@ -15778,31 +15773,32 @@ namespace ts {
1577815773
let nakedTypeVariable: Type | undefined;
1577915774
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
1578015775
const matched = new Array<boolean>(sources.length);
15781-
const saveInferenceIncomplete = inferenceIncomplete;
15782-
inferenceIncomplete = false;
15776+
let inferenceCircularity = false;
1578315777
// First infer to types that are not naked type variables. For each source type we
15784-
// track whether inferences were made from that particular type to some target.
15778+
// track whether inferences were made from that particular type to some target with
15779+
// equal priority (i.e. of equal quality) to what we would infer for a naked type
15780+
// parameter.
1578515781
for (const t of targets) {
1578615782
if (getInferenceInfoForType(t)) {
1578715783
nakedTypeVariable = t;
1578815784
typeVariableCount++;
1578915785
}
1579015786
else {
1579115787
for (let i = 0; i < sources.length; i++) {
15792-
const saveInferenceMatch = inferenceMatch;
15793-
inferenceMatch = false;
15788+
const saveInferencePriority = inferencePriority;
15789+
inferencePriority = InferencePriority.MaxValue;
1579415790
inferFromTypes(sources[i], t);
15795-
if (inferenceMatch) matched[i] = true;
15796-
inferenceMatch = inferenceMatch || saveInferenceMatch;
15791+
if (inferencePriority === priority) matched[i] = true;
15792+
inferenceCircularity = inferenceCircularity || inferencePriority < 0;
15793+
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
1579715794
}
1579815795
}
1579915796
}
15800-
const inferenceComplete = !inferenceIncomplete;
15801-
inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete;
15802-
// If the target has a single naked type variable and inference completed (meaning we
15803-
// explored the types fully), create a union of the source types from which no inferences
15804-
// have been made so far and infer from that union to the naked type variable.
15805-
if (typeVariableCount === 1 && inferenceComplete) {
15797+
// If the target has a single naked type variable and no inference circularities were
15798+
// encountered above (meaning we explored the types fully), create a union of the source
15799+
// types from which no inferences have been made so far and infer from that union to the
15800+
// naked type variable.
15801+
if (typeVariableCount === 1 && !inferenceCircularity) {
1580615802
const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s);
1580715803
if (unmatched.length) {
1580815804
inferFromTypes(getUnionType(unmatched), nakedTypeVariable!);
@@ -15905,7 +15901,7 @@ namespace ts {
1590515901
const symbol = isNonConstructorObject ? target.symbol : undefined;
1590615902
if (symbol) {
1590715903
if (contains(symbolStack, symbol)) {
15908-
inferenceIncomplete = true;
15904+
inferencePriority = -1;
1590915905
return;
1591015906
}
1591115907
(symbolStack || (symbolStack = [])).push(symbol);

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4471,6 +4471,7 @@ namespace ts {
44714471
LiteralKeyof = 1 << 5, // Inference made from a string literal to a keyof T
44724472
NoConstraints = 1 << 6, // Don't infer from constraints of instantiable types
44734473
AlwaysStrict = 1 << 7, // Always use strict rules for contravariant inferences
4474+
MaxValue = 1 << 8, // Seed for inference priority tracking
44744475

44754476
PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates
44764477
}

0 commit comments

Comments
 (0)