@@ -8126,6 +8126,25 @@ namespace ts {
81268126 return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
81278127 }
81288128
8129+ function isTypeSubsetOf(source: Type, target: Type) {
8130+ return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, <UnionType>target);
8131+ }
8132+
8133+ function isTypeSubsetOfUnion(source: Type, target: UnionType) {
8134+ if (source.flags & TypeFlags.Union) {
8135+ for (const t of (<UnionType>source).types) {
8136+ if (!containsType(target.types, t)) {
8137+ return false;
8138+ }
8139+ }
8140+ return true;
8141+ }
8142+ if (source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.Enum && (<EnumLiteralType>source).baseType === target) {
8143+ return true;
8144+ }
8145+ return containsType(target.types, source);
8146+ }
8147+
81298148 function filterType(type: Type, f: (t: Type) => boolean): Type {
81308149 return type.flags & TypeFlags.Union ?
81318150 getUnionType(filter((<UnionType>type).types, f)) :
@@ -8272,6 +8291,7 @@ namespace ts {
82728291
82738292 function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
82748293 const antecedentTypes: Type[] = [];
8294+ let subtypeReduction = false;
82758295 let seenIncomplete = false;
82768296 for (const antecedent of flow.antecedents) {
82778297 const flowType = getTypeAtFlowNode(antecedent);
@@ -8286,11 +8306,17 @@ namespace ts {
82868306 if (!contains(antecedentTypes, type)) {
82878307 antecedentTypes.push(type);
82888308 }
8309+ // If an antecedent type is not a subset of the declared type, we need to perform
8310+ // subtype reduction. This happens when a "foreign" type is injected into the control
8311+ // flow using the instanceof operator or a user defined type predicate.
8312+ if (!isTypeSubsetOf(type, declaredType)) {
8313+ subtypeReduction = true;
8314+ }
82898315 if (isIncomplete(flowType)) {
82908316 seenIncomplete = true;
82918317 }
82928318 }
8293- return createFlowType(getUnionType(antecedentTypes), seenIncomplete);
8319+ return createFlowType(getUnionType(antecedentTypes, subtypeReduction ), seenIncomplete);
82948320 }
82958321
82968322 function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
@@ -8316,6 +8342,7 @@ namespace ts {
83168342 // Add the flow loop junction and reference to the in-process stack and analyze
83178343 // each antecedent code path.
83188344 const antecedentTypes: Type[] = [];
8345+ let subtypeReduction = false;
83198346 flowLoopNodes[flowLoopCount] = flow;
83208347 flowLoopKeys[flowLoopCount] = key;
83218348 flowLoopTypes[flowLoopCount] = antecedentTypes;
@@ -8332,14 +8359,20 @@ namespace ts {
83328359 if (!contains(antecedentTypes, type)) {
83338360 antecedentTypes.push(type);
83348361 }
8362+ // If an antecedent type is not a subset of the declared type, we need to perform
8363+ // subtype reduction. This happens when a "foreign" type is injected into the control
8364+ // flow using the instanceof operator or a user defined type predicate.
8365+ if (!isTypeSubsetOf(type, declaredType)) {
8366+ subtypeReduction = true;
8367+ }
83358368 // If the type at a particular antecedent path is the declared type there is no
83368369 // reason to process more antecedents since the only possible outcome is subtypes
83378370 // that will be removed in the final union type anyway.
83388371 if (type === declaredType) {
83398372 break;
83408373 }
83418374 }
8342- return cache[key] = getUnionType(antecedentTypes);
8375+ return cache[key] = getUnionType(antecedentTypes, subtypeReduction );
83438376 }
83448377
83458378 function isMatchingReferenceDiscriminant(expr: Expression) {
@@ -8537,9 +8570,7 @@ namespace ts {
85378570
85388571 function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean) {
85398572 if (!assumeTrue) {
8540- return type.flags & TypeFlags.Union ?
8541- getUnionType(filter((<UnionType>type).types, t => !isTypeSubtypeOf(t, candidate))) :
8542- type;
8573+ return filterType(type, t => !isTypeSubtypeOf(t, candidate));
85438574 }
85448575 // If the current type is a union type, remove all constituents that aren't assignable to
85458576 // the candidate type. If one or more constituents remain, return a union of those.
@@ -8549,13 +8580,16 @@ namespace ts {
85498580 return getUnionType(assignableConstituents);
85508581 }
85518582 }
8552- // If the candidate type is assignable to the target type, narrow to the candidate type.
8553- // Otherwise, if the current type is assignable to the candidate, keep the current type.
8554- // Otherwise, the types are completely unrelated, so narrow to the empty type.
8583+ // If the candidate type is a subtype of the target type, narrow to the candidate type.
8584+ // Otherwise, if the target type is assignable to the candidate type, keep the target type.
8585+ // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
8586+ // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
8587+ // two types.
85558588 const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
8556- return isTypeAssignableTo (candidate, targetType) ? candidate :
8589+ return isTypeSubtypeOf (candidate, targetType) ? candidate :
85578590 isTypeAssignableTo(type, candidate) ? type :
8558- getIntersectionType([type, candidate]);
8591+ isTypeAssignableTo(candidate, targetType) ? candidate :
8592+ getIntersectionType([type, candidate]);
85598593 }
85608594
85618595 function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
0 commit comments