Merge pull request #10194 from Microsoft/fixInstanceofNarrowing

Fix instanceof narrowing
This commit is contained in:
Anders Hejlsberg
2016-08-08 11:51:34 -07:00
committed by GitHub
14 changed files with 1112 additions and 19 deletions

View File

@@ -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 {