@@ -8084,6 +8084,25 @@ namespace ts {
8084
8084
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
8085
8085
}
8086
8086
8087
+ function isTypeSubsetOf(source: Type, target: Type) {
8088
+ return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, <UnionType>target);
8089
+ }
8090
+
8091
+ function isTypeSubsetOfUnion(source: Type, target: UnionType) {
8092
+ if (source.flags & TypeFlags.Union) {
8093
+ for (const t of (<UnionType>source).types) {
8094
+ if (!containsType(target.types, t)) {
8095
+ return false;
8096
+ }
8097
+ }
8098
+ return true;
8099
+ }
8100
+ if (source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.Enum && (<EnumLiteralType>source).baseType === target) {
8101
+ return true;
8102
+ }
8103
+ return containsType(target.types, source);
8104
+ }
8105
+
8087
8106
function filterType(type: Type, f: (t: Type) => boolean): Type {
8088
8107
return type.flags & TypeFlags.Union ?
8089
8108
getUnionType(filter((<UnionType>type).types, f)) :
@@ -8230,6 +8249,7 @@ namespace ts {
8230
8249
8231
8250
function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
8232
8251
const antecedentTypes: Type[] = [];
8252
+ let subtypeReduction = false;
8233
8253
let seenIncomplete = false;
8234
8254
for (const antecedent of flow.antecedents) {
8235
8255
const flowType = getTypeAtFlowNode(antecedent);
@@ -8244,11 +8264,17 @@ namespace ts {
8244
8264
if (!contains(antecedentTypes, type)) {
8245
8265
antecedentTypes.push(type);
8246
8266
}
8267
+ // If an antecedent type is not a subset of the declared type, we need to perform
8268
+ // subtype reduction. This happens when a "foreign" type is injected into the control
8269
+ // flow using the instanceof operator or a user defined type predicate.
8270
+ if (!isTypeSubsetOf(type, declaredType)) {
8271
+ subtypeReduction = true;
8272
+ }
8247
8273
if (isIncomplete(flowType)) {
8248
8274
seenIncomplete = true;
8249
8275
}
8250
8276
}
8251
- return createFlowType(getUnionType(antecedentTypes), seenIncomplete);
8277
+ return createFlowType(getUnionType(antecedentTypes, subtypeReduction ), seenIncomplete);
8252
8278
}
8253
8279
8254
8280
function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
@@ -8274,6 +8300,7 @@ namespace ts {
8274
8300
// Add the flow loop junction and reference to the in-process stack and analyze
8275
8301
// each antecedent code path.
8276
8302
const antecedentTypes: Type[] = [];
8303
+ let subtypeReduction = false;
8277
8304
flowLoopNodes[flowLoopCount] = flow;
8278
8305
flowLoopKeys[flowLoopCount] = key;
8279
8306
flowLoopTypes[flowLoopCount] = antecedentTypes;
@@ -8290,14 +8317,20 @@ namespace ts {
8290
8317
if (!contains(antecedentTypes, type)) {
8291
8318
antecedentTypes.push(type);
8292
8319
}
8320
+ // If an antecedent type is not a subset of the declared type, we need to perform
8321
+ // subtype reduction. This happens when a "foreign" type is injected into the control
8322
+ // flow using the instanceof operator or a user defined type predicate.
8323
+ if (!isTypeSubsetOf(type, declaredType)) {
8324
+ subtypeReduction = true;
8325
+ }
8293
8326
// If the type at a particular antecedent path is the declared type there is no
8294
8327
// reason to process more antecedents since the only possible outcome is subtypes
8295
8328
// that will be removed in the final union type anyway.
8296
8329
if (type === declaredType) {
8297
8330
break;
8298
8331
}
8299
8332
}
8300
- return cache[key] = getUnionType(antecedentTypes);
8333
+ return cache[key] = getUnionType(antecedentTypes, subtypeReduction );
8301
8334
}
8302
8335
8303
8336
function isMatchingPropertyAccess(expr: Expression) {
@@ -8494,9 +8527,7 @@ namespace ts {
8494
8527
8495
8528
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean) {
8496
8529
if (!assumeTrue) {
8497
- return type.flags & TypeFlags.Union ?
8498
- getUnionType(filter((<UnionType>type).types, t => !isTypeSubtypeOf(t, candidate))) :
8499
- type;
8530
+ return filterType(type, t => !isTypeSubtypeOf(t, candidate));
8500
8531
}
8501
8532
// If the current type is a union type, remove all constituents that aren't assignable to
8502
8533
// the candidate type. If one or more constituents remain, return a union of those.
@@ -8506,13 +8537,15 @@ namespace ts {
8506
8537
return getUnionType(assignableConstituents);
8507
8538
}
8508
8539
}
8509
- // If the candidate type is assignable to the target type, narrow to the candidate type.
8510
- // Otherwise, if the current type is assignable to the candidate, keep the current type.
8511
- // Otherwise, the types are completely unrelated, so narrow to the empty type.
8540
+ // If the candidate type is a subtype of the target type, narrow to the candidate type.
8541
+ // Otherwise, narrow to whichever of the target type or the candidate type that is assignable
8542
+ // to the other. Otherwise, the types are completely unrelated, so narrow to an intersection
8543
+ // of the two types.
8512
8544
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
8513
- return isTypeAssignableTo (candidate, targetType) ? candidate :
8545
+ return isTypeSubtypeOf (candidate, targetType) ? candidate :
8514
8546
isTypeAssignableTo(type, candidate) ? type :
8515
- getIntersectionType([type, candidate]);
8547
+ isTypeAssignableTo(candidate, targetType) ? candidate :
8548
+ getIntersectionType([type, candidate]);
8516
8549
}
8517
8550
8518
8551
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
0 commit comments