mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 21:36:50 -05:00
Merge pull request #10194 from Microsoft/fixInstanceofNarrowing
Fix instanceof narrowing
This commit is contained in:
@@ -8126,6 +8126,25 @@ namespace ts {
|
||||
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
|
||||
}
|
||||
|
||||
function isTypeSubsetOf(source: Type, target: Type) {
|
||||
return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, <UnionType>target);
|
||||
}
|
||||
|
||||
function isTypeSubsetOfUnion(source: Type, target: UnionType) {
|
||||
if (source.flags & TypeFlags.Union) {
|
||||
for (const t of (<UnionType>source).types) {
|
||||
if (!containsType(target.types, t)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.Enum && (<EnumLiteralType>source).baseType === target) {
|
||||
return true;
|
||||
}
|
||||
return containsType(target.types, source);
|
||||
}
|
||||
|
||||
function filterType(type: Type, f: (t: Type) => boolean): Type {
|
||||
return type.flags & TypeFlags.Union ?
|
||||
getUnionType(filter((<UnionType>type).types, f)) :
|
||||
@@ -8272,6 +8291,7 @@ namespace ts {
|
||||
|
||||
function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
|
||||
const antecedentTypes: Type[] = [];
|
||||
let subtypeReduction = false;
|
||||
let seenIncomplete = false;
|
||||
for (const antecedent of flow.antecedents) {
|
||||
const flowType = getTypeAtFlowNode(antecedent);
|
||||
@@ -8286,11 +8306,17 @@ namespace ts {
|
||||
if (!contains(antecedentTypes, type)) {
|
||||
antecedentTypes.push(type);
|
||||
}
|
||||
// If an antecedent type is not a subset of the declared type, we need to perform
|
||||
// subtype reduction. This happens when a "foreign" type is injected into the control
|
||||
// flow using the instanceof operator or a user defined type predicate.
|
||||
if (!isTypeSubsetOf(type, declaredType)) {
|
||||
subtypeReduction = true;
|
||||
}
|
||||
if (isIncomplete(flowType)) {
|
||||
seenIncomplete = true;
|
||||
}
|
||||
}
|
||||
return createFlowType(getUnionType(antecedentTypes), seenIncomplete);
|
||||
return createFlowType(getUnionType(antecedentTypes, subtypeReduction), seenIncomplete);
|
||||
}
|
||||
|
||||
function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
|
||||
@@ -8316,6 +8342,7 @@ namespace ts {
|
||||
// Add the flow loop junction and reference to the in-process stack and analyze
|
||||
// each antecedent code path.
|
||||
const antecedentTypes: Type[] = [];
|
||||
let subtypeReduction = false;
|
||||
flowLoopNodes[flowLoopCount] = flow;
|
||||
flowLoopKeys[flowLoopCount] = key;
|
||||
flowLoopTypes[flowLoopCount] = antecedentTypes;
|
||||
@@ -8332,6 +8359,12 @@ namespace ts {
|
||||
if (!contains(antecedentTypes, type)) {
|
||||
antecedentTypes.push(type);
|
||||
}
|
||||
// If an antecedent type is not a subset of the declared type, we need to perform
|
||||
// subtype reduction. This happens when a "foreign" type is injected into the control
|
||||
// flow using the instanceof operator or a user defined type predicate.
|
||||
if (!isTypeSubsetOf(type, declaredType)) {
|
||||
subtypeReduction = true;
|
||||
}
|
||||
// If the type at a particular antecedent path is the declared type there is no
|
||||
// reason to process more antecedents since the only possible outcome is subtypes
|
||||
// that will be removed in the final union type anyway.
|
||||
@@ -8339,7 +8372,7 @@ namespace ts {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cache[key] = getUnionType(antecedentTypes);
|
||||
return cache[key] = getUnionType(antecedentTypes, subtypeReduction);
|
||||
}
|
||||
|
||||
function isMatchingReferenceDiscriminant(expr: Expression) {
|
||||
@@ -8537,9 +8570,7 @@ namespace ts {
|
||||
|
||||
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean) {
|
||||
if (!assumeTrue) {
|
||||
return type.flags & TypeFlags.Union ?
|
||||
getUnionType(filter((<UnionType>type).types, t => !isTypeSubtypeOf(t, candidate))) :
|
||||
type;
|
||||
return filterType(type, t => !isTypeSubtypeOf(t, candidate));
|
||||
}
|
||||
// If the current type is a union type, remove all constituents that aren't assignable to
|
||||
// the candidate type. If one or more constituents remain, return a union of those.
|
||||
@@ -8549,13 +8580,16 @@ namespace ts {
|
||||
return getUnionType(assignableConstituents);
|
||||
}
|
||||
}
|
||||
// If the candidate type is assignable to the target type, narrow to the candidate type.
|
||||
// Otherwise, if the current type is assignable to the candidate, keep the current type.
|
||||
// Otherwise, the types are completely unrelated, so narrow to the empty type.
|
||||
// If the candidate type is a subtype of the target type, narrow to the candidate type.
|
||||
// Otherwise, if the target type is assignable to the candidate type, keep the target type.
|
||||
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
|
||||
// type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
|
||||
// two types.
|
||||
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
|
||||
return isTypeAssignableTo(candidate, targetType) ? candidate :
|
||||
return isTypeSubtypeOf(candidate, targetType) ? candidate :
|
||||
isTypeAssignableTo(type, candidate) ? type :
|
||||
getIntersectionType([type, candidate]);
|
||||
isTypeAssignableTo(candidate, targetType) ? candidate :
|
||||
getIntersectionType([type, candidate]);
|
||||
}
|
||||
|
||||
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
|
||||
|
||||
Reference in New Issue
Block a user