@@ -8126,6 +8126,25 @@ namespace ts {
8126
8126
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
8127
8127
}
8128
8128
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
+
8129
8148
function filterType(type: Type, f: (t: Type) => boolean): Type {
8130
8149
return type.flags & TypeFlags.Union ?
8131
8150
getUnionType(filter((<UnionType>type).types, f)) :
@@ -8272,6 +8291,7 @@ namespace ts {
8272
8291
8273
8292
function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
8274
8293
const antecedentTypes: Type[] = [];
8294
+ let subtypeReduction = false;
8275
8295
let seenIncomplete = false;
8276
8296
for (const antecedent of flow.antecedents) {
8277
8297
const flowType = getTypeAtFlowNode(antecedent);
@@ -8286,11 +8306,17 @@ namespace ts {
8286
8306
if (!contains(antecedentTypes, type)) {
8287
8307
antecedentTypes.push(type);
8288
8308
}
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
+ }
8289
8315
if (isIncomplete(flowType)) {
8290
8316
seenIncomplete = true;
8291
8317
}
8292
8318
}
8293
- return createFlowType(getUnionType(antecedentTypes), seenIncomplete);
8319
+ return createFlowType(getUnionType(antecedentTypes, subtypeReduction ), seenIncomplete);
8294
8320
}
8295
8321
8296
8322
function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
@@ -8316,6 +8342,7 @@ namespace ts {
8316
8342
// Add the flow loop junction and reference to the in-process stack and analyze
8317
8343
// each antecedent code path.
8318
8344
const antecedentTypes: Type[] = [];
8345
+ let subtypeReduction = false;
8319
8346
flowLoopNodes[flowLoopCount] = flow;
8320
8347
flowLoopKeys[flowLoopCount] = key;
8321
8348
flowLoopTypes[flowLoopCount] = antecedentTypes;
@@ -8332,14 +8359,20 @@ namespace ts {
8332
8359
if (!contains(antecedentTypes, type)) {
8333
8360
antecedentTypes.push(type);
8334
8361
}
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
+ }
8335
8368
// If the type at a particular antecedent path is the declared type there is no
8336
8369
// reason to process more antecedents since the only possible outcome is subtypes
8337
8370
// that will be removed in the final union type anyway.
8338
8371
if (type === declaredType) {
8339
8372
break;
8340
8373
}
8341
8374
}
8342
- return cache[key] = getUnionType(antecedentTypes);
8375
+ return cache[key] = getUnionType(antecedentTypes, subtypeReduction );
8343
8376
}
8344
8377
8345
8378
function isMatchingReferenceDiscriminant(expr: Expression) {
@@ -8537,9 +8570,7 @@ namespace ts {
8537
8570
8538
8571
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean) {
8539
8572
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));
8543
8574
}
8544
8575
// If the current type is a union type, remove all constituents that aren't assignable to
8545
8576
// the candidate type. If one or more constituents remain, return a union of those.
@@ -8549,13 +8580,16 @@ namespace ts {
8549
8580
return getUnionType(assignableConstituents);
8550
8581
}
8551
8582
}
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.
8555
8588
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
8556
- return isTypeAssignableTo (candidate, targetType) ? candidate :
8589
+ return isTypeSubtypeOf (candidate, targetType) ? candidate :
8557
8590
isTypeAssignableTo(type, candidate) ? type :
8558
- getIntersectionType([type, candidate]);
8591
+ isTypeAssignableTo(candidate, targetType) ? candidate :
8592
+ getIntersectionType([type, candidate]);
8559
8593
}
8560
8594
8561
8595
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
0 commit comments