@@ -4416,10 +4416,19 @@ namespace ts {
4416
4416
}
4417
4417
const propTypes: Type[] = [];
4418
4418
const declarations: Declaration[] = [];
4419
+ let commonType: Type = undefined;
4420
+ let hasCommonType = true;
4419
4421
for (const prop of props) {
4420
4422
if (prop.declarations) {
4421
4423
addRange(declarations, prop.declarations);
4422
4424
}
4425
+ const type = getTypeOfSymbol(prop);
4426
+ if (!commonType) {
4427
+ commonType = type;
4428
+ }
4429
+ else if (type !== commonType) {
4430
+ hasCommonType = false;
4431
+ }
4423
4432
propTypes.push(getTypeOfSymbol(prop));
4424
4433
}
4425
4434
const result = <TransientSymbol>createSymbol(
@@ -4429,6 +4438,7 @@ namespace ts {
4429
4438
commonFlags,
4430
4439
name);
4431
4440
result.containingType = containingType;
4441
+ result.hasCommonType = hasCommonType;
4432
4442
result.declarations = declarations;
4433
4443
result.isReadonly = isReadonly;
4434
4444
result.type = containingType.flags & TypeFlags.Union ? getUnionType(propTypes) : getIntersectionType(propTypes);
@@ -7798,8 +7808,39 @@ namespace ts {
7798
7808
return false;
7799
7809
}
7800
7810
7801
- function rootContainsMatchingReference(source: Node, target: Node) {
7802
- return target.kind === SyntaxKind.PropertyAccessExpression && containsMatchingReference(source, (<PropertyAccessExpression>target).expression);
7811
+ // Return true if target is a property access xxx.yyy, source is a property access xxx.zzz, the declared
7812
+ // type of xxx is a union type, and yyy is a property that is possibly a discriminant. We consider a property
7813
+ // a possible discriminant if its type differs in the constituents of containing union type, and if every
7814
+ // choice is a unit type or a union of unit types.
7815
+ function containsMatchingReferenceDiscriminant(source: Node, target: Node) {
7816
+ return target.kind === SyntaxKind.PropertyAccessExpression &&
7817
+ containsMatchingReference(source, (<PropertyAccessExpression>target).expression) &&
7818
+ isDiscriminantProperty(getDeclaredTypeOfReference((<PropertyAccessExpression>target).expression), (<PropertyAccessExpression>target).name.text);
7819
+ }
7820
+
7821
+ function getDeclaredTypeOfReference(expr: Node): Type {
7822
+ if (expr.kind === SyntaxKind.Identifier) {
7823
+ return getTypeOfSymbol(getResolvedSymbol(<Identifier>expr));
7824
+ }
7825
+ if (expr.kind === SyntaxKind.PropertyAccessExpression) {
7826
+ const type = getDeclaredTypeOfReference((<PropertyAccessExpression>expr).expression);
7827
+ return type && getTypeOfPropertyOfType(type, (<PropertyAccessExpression>expr).name.text);
7828
+ }
7829
+ return undefined;
7830
+ }
7831
+
7832
+ function isDiscriminantProperty(type: Type, name: string) {
7833
+ if (type && type.flags & TypeFlags.Union) {
7834
+ const prop = getPropertyOfType(type, name);
7835
+ if (prop && prop.flags & SymbolFlags.SyntheticProperty) {
7836
+ if ((<TransientSymbol>prop).isDiscriminantProperty === undefined) {
7837
+ (<TransientSymbol>prop).isDiscriminantProperty = !(<TransientSymbol>prop).hasCommonType &&
7838
+ isUnitUnionType(getTypeOfSymbol(prop));
7839
+ }
7840
+ return (<TransientSymbol>prop).isDiscriminantProperty;
7841
+ }
7842
+ }
7843
+ return false;
7803
7844
}
7804
7845
7805
7846
function isOrContainsMatchingReference(source: Node, target: Node) {
@@ -8090,6 +8131,25 @@ namespace ts {
8090
8131
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
8091
8132
}
8092
8133
8134
+ function isTypeSubsetOf(source: Type, target: Type) {
8135
+ return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, <UnionType>target);
8136
+ }
8137
+
8138
+ function isTypeSubsetOfUnion(source: Type, target: UnionType) {
8139
+ if (source.flags & TypeFlags.Union) {
8140
+ for (const t of (<UnionType>source).types) {
8141
+ if (!containsType(target.types, t)) {
8142
+ return false;
8143
+ }
8144
+ }
8145
+ return true;
8146
+ }
8147
+ if (source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.Enum && (<EnumLiteralType>source).baseType === target) {
8148
+ return true;
8149
+ }
8150
+ return containsType(target.types, source);
8151
+ }
8152
+
8093
8153
function filterType(type: Type, f: (t: Type) => boolean): Type {
8094
8154
return type.flags & TypeFlags.Union ?
8095
8155
getUnionType(filter((<UnionType>type).types, f)) :
@@ -8228,14 +8288,15 @@ namespace ts {
8228
8288
if (isMatchingReference(reference, expr)) {
8229
8289
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
8230
8290
}
8231
- else if (isMatchingPropertyAccess (expr)) {
8291
+ else if (isMatchingReferenceDiscriminant (expr)) {
8232
8292
type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
8233
8293
}
8234
8294
return createFlowType(type, isIncomplete(flowType));
8235
8295
}
8236
8296
8237
8297
function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
8238
8298
const antecedentTypes: Type[] = [];
8299
+ let subtypeReduction = false;
8239
8300
let seenIncomplete = false;
8240
8301
for (const antecedent of flow.antecedents) {
8241
8302
const flowType = getTypeAtFlowNode(antecedent);
@@ -8250,11 +8311,17 @@ namespace ts {
8250
8311
if (!contains(antecedentTypes, type)) {
8251
8312
antecedentTypes.push(type);
8252
8313
}
8314
+ // If an antecedent type is not a subset of the declared type, we need to perform
8315
+ // subtype reduction. This happens when a "foreign" type is injected into the control
8316
+ // flow using the instanceof operator or a user defined type predicate.
8317
+ if (!isTypeSubsetOf(type, declaredType)) {
8318
+ subtypeReduction = true;
8319
+ }
8253
8320
if (isIncomplete(flowType)) {
8254
8321
seenIncomplete = true;
8255
8322
}
8256
8323
}
8257
- return createFlowType(getUnionType(antecedentTypes), seenIncomplete);
8324
+ return createFlowType(getUnionType(antecedentTypes, subtypeReduction ), seenIncomplete);
8258
8325
}
8259
8326
8260
8327
function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
@@ -8280,6 +8347,7 @@ namespace ts {
8280
8347
// Add the flow loop junction and reference to the in-process stack and analyze
8281
8348
// each antecedent code path.
8282
8349
const antecedentTypes: Type[] = [];
8350
+ let subtypeReduction = false;
8283
8351
flowLoopNodes[flowLoopCount] = flow;
8284
8352
flowLoopKeys[flowLoopCount] = key;
8285
8353
flowLoopTypes[flowLoopCount] = antecedentTypes;
@@ -8296,20 +8364,27 @@ namespace ts {
8296
8364
if (!contains(antecedentTypes, type)) {
8297
8365
antecedentTypes.push(type);
8298
8366
}
8367
+ // If an antecedent type is not a subset of the declared type, we need to perform
8368
+ // subtype reduction. This happens when a "foreign" type is injected into the control
8369
+ // flow using the instanceof operator or a user defined type predicate.
8370
+ if (!isTypeSubsetOf(type, declaredType)) {
8371
+ subtypeReduction = true;
8372
+ }
8299
8373
// If the type at a particular antecedent path is the declared type there is no
8300
8374
// reason to process more antecedents since the only possible outcome is subtypes
8301
8375
// that will be removed in the final union type anyway.
8302
8376
if (type === declaredType) {
8303
8377
break;
8304
8378
}
8305
8379
}
8306
- return cache[key] = getUnionType(antecedentTypes);
8380
+ return cache[key] = getUnionType(antecedentTypes, subtypeReduction );
8307
8381
}
8308
8382
8309
- function isMatchingPropertyAccess (expr: Expression) {
8383
+ function isMatchingReferenceDiscriminant (expr: Expression) {
8310
8384
return expr.kind === SyntaxKind.PropertyAccessExpression &&
8385
+ declaredType.flags & TypeFlags.Union &&
8311
8386
isMatchingReference(reference, (<PropertyAccessExpression>expr).expression) &&
8312
- (declaredType.flags & TypeFlags.Union) !== 0 ;
8387
+ isDiscriminantProperty (declaredType, (<PropertyAccessExpression>expr).name.text) ;
8313
8388
}
8314
8389
8315
8390
function narrowTypeByDiscriminant(type: Type, propAccess: PropertyAccessExpression, narrowType: (t: Type) => Type): Type {
@@ -8323,10 +8398,10 @@ namespace ts {
8323
8398
if (isMatchingReference(reference, expr)) {
8324
8399
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
8325
8400
}
8326
- if (isMatchingPropertyAccess (expr)) {
8401
+ if (isMatchingReferenceDiscriminant (expr)) {
8327
8402
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
8328
8403
}
8329
- if (rootContainsMatchingReference (reference, expr)) {
8404
+ if (containsMatchingReferenceDiscriminant (reference, expr)) {
8330
8405
return declaredType;
8331
8406
}
8332
8407
return type;
@@ -8355,13 +8430,13 @@ namespace ts {
8355
8430
if (isMatchingReference(reference, right)) {
8356
8431
return narrowTypeByEquality(type, operator, left, assumeTrue);
8357
8432
}
8358
- if (isMatchingPropertyAccess (left)) {
8433
+ if (isMatchingReferenceDiscriminant (left)) {
8359
8434
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
8360
8435
}
8361
- if (isMatchingPropertyAccess (right)) {
8436
+ if (isMatchingReferenceDiscriminant (right)) {
8362
8437
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
8363
8438
}
8364
- if (rootContainsMatchingReference (reference, left) || rootContainsMatchingReference (reference, right)) {
8439
+ if (containsMatchingReferenceDiscriminant (reference, left) || containsMatchingReferenceDiscriminant (reference, right)) {
8365
8440
return declaredType;
8366
8441
}
8367
8442
break;
@@ -8500,9 +8575,7 @@ namespace ts {
8500
8575
8501
8576
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean) {
8502
8577
if (!assumeTrue) {
8503
- return type.flags & TypeFlags.Union ?
8504
- getUnionType(filter((<UnionType>type).types, t => !isTypeSubtypeOf(t, candidate))) :
8505
- type;
8578
+ return filterType(type, t => !isTypeSubtypeOf(t, candidate));
8506
8579
}
8507
8580
// If the current type is a union type, remove all constituents that aren't assignable to
8508
8581
// the candidate type. If one or more constituents remain, return a union of those.
@@ -8512,13 +8585,16 @@ namespace ts {
8512
8585
return getUnionType(assignableConstituents);
8513
8586
}
8514
8587
}
8515
- // If the candidate type is assignable to the target type, narrow to the candidate type.
8516
- // Otherwise, if the current type is assignable to the candidate, keep the current type.
8517
- // Otherwise, the types are completely unrelated, so narrow to the empty type.
8588
+ // If the candidate type is a subtype of the target type, narrow to the candidate type.
8589
+ // Otherwise, if the target type is assignable to the candidate type, keep the target type.
8590
+ // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
8591
+ // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
8592
+ // two types.
8518
8593
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
8519
- return isTypeAssignableTo (candidate, targetType) ? candidate :
8594
+ return isTypeSubtypeOf (candidate, targetType) ? candidate :
8520
8595
isTypeAssignableTo(type, candidate) ? type :
8521
- getIntersectionType([type, candidate]);
8596
+ isTypeAssignableTo(candidate, targetType) ? candidate :
8597
+ getIntersectionType([type, candidate]);
8522
8598
}
8523
8599
8524
8600
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
0 commit comments