Skip to content

Commit 669191e

Browse files
author
Kanchalai Tanglertsampan
committed
Merge branch 'master' of https://github.com/microsoft/TypeScript
2 parents 9a18429 + 30e95df commit 669191e

19 files changed

+1424
-30
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ node_js:
88
sudo: false
99

1010
env:
11-
- workerCount=4
11+
- workerCount=3
1212

1313
matrix:
1414
fast_finish: true

src/compiler/checker.ts

Lines changed: 96 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4416,10 +4416,19 @@ namespace ts {
44164416
}
44174417
const propTypes: Type[] = [];
44184418
const declarations: Declaration[] = [];
4419+
let commonType: Type = undefined;
4420+
let hasCommonType = true;
44194421
for (const prop of props) {
44204422
if (prop.declarations) {
44214423
addRange(declarations, prop.declarations);
44224424
}
4425+
const type = getTypeOfSymbol(prop);
4426+
if (!commonType) {
4427+
commonType = type;
4428+
}
4429+
else if (type !== commonType) {
4430+
hasCommonType = false;
4431+
}
44234432
propTypes.push(getTypeOfSymbol(prop));
44244433
}
44254434
const result = <TransientSymbol>createSymbol(
@@ -4429,6 +4438,7 @@ namespace ts {
44294438
commonFlags,
44304439
name);
44314440
result.containingType = containingType;
4441+
result.hasCommonType = hasCommonType;
44324442
result.declarations = declarations;
44334443
result.isReadonly = isReadonly;
44344444
result.type = containingType.flags & TypeFlags.Union ? getUnionType(propTypes) : getIntersectionType(propTypes);
@@ -7798,8 +7808,39 @@ namespace ts {
77987808
return false;
77997809
}
78007810

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;
78037844
}
78047845

78057846
function isOrContainsMatchingReference(source: Node, target: Node) {
@@ -8090,6 +8131,25 @@ namespace ts {
80908131
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
80918132
}
80928133

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+
80938153
function filterType(type: Type, f: (t: Type) => boolean): Type {
80948154
return type.flags & TypeFlags.Union ?
80958155
getUnionType(filter((<UnionType>type).types, f)) :
@@ -8228,14 +8288,15 @@ namespace ts {
82288288
if (isMatchingReference(reference, expr)) {
82298289
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
82308290
}
8231-
else if (isMatchingPropertyAccess(expr)) {
8291+
else if (isMatchingReferenceDiscriminant(expr)) {
82328292
type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
82338293
}
82348294
return createFlowType(type, isIncomplete(flowType));
82358295
}
82368296

82378297
function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
82388298
const antecedentTypes: Type[] = [];
8299+
let subtypeReduction = false;
82398300
let seenIncomplete = false;
82408301
for (const antecedent of flow.antecedents) {
82418302
const flowType = getTypeAtFlowNode(antecedent);
@@ -8250,11 +8311,17 @@ namespace ts {
82508311
if (!contains(antecedentTypes, type)) {
82518312
antecedentTypes.push(type);
82528313
}
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+
}
82538320
if (isIncomplete(flowType)) {
82548321
seenIncomplete = true;
82558322
}
82568323
}
8257-
return createFlowType(getUnionType(antecedentTypes), seenIncomplete);
8324+
return createFlowType(getUnionType(antecedentTypes, subtypeReduction), seenIncomplete);
82588325
}
82598326

82608327
function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
@@ -8280,6 +8347,7 @@ namespace ts {
82808347
// Add the flow loop junction and reference to the in-process stack and analyze
82818348
// each antecedent code path.
82828349
const antecedentTypes: Type[] = [];
8350+
let subtypeReduction = false;
82838351
flowLoopNodes[flowLoopCount] = flow;
82848352
flowLoopKeys[flowLoopCount] = key;
82858353
flowLoopTypes[flowLoopCount] = antecedentTypes;
@@ -8296,20 +8364,27 @@ namespace ts {
82968364
if (!contains(antecedentTypes, type)) {
82978365
antecedentTypes.push(type);
82988366
}
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+
}
82998373
// If the type at a particular antecedent path is the declared type there is no
83008374
// reason to process more antecedents since the only possible outcome is subtypes
83018375
// that will be removed in the final union type anyway.
83028376
if (type === declaredType) {
83038377
break;
83048378
}
83058379
}
8306-
return cache[key] = getUnionType(antecedentTypes);
8380+
return cache[key] = getUnionType(antecedentTypes, subtypeReduction);
83078381
}
83088382

8309-
function isMatchingPropertyAccess(expr: Expression) {
8383+
function isMatchingReferenceDiscriminant(expr: Expression) {
83108384
return expr.kind === SyntaxKind.PropertyAccessExpression &&
8385+
declaredType.flags & TypeFlags.Union &&
83118386
isMatchingReference(reference, (<PropertyAccessExpression>expr).expression) &&
8312-
(declaredType.flags & TypeFlags.Union) !== 0;
8387+
isDiscriminantProperty(declaredType, (<PropertyAccessExpression>expr).name.text);
83138388
}
83148389

83158390
function narrowTypeByDiscriminant(type: Type, propAccess: PropertyAccessExpression, narrowType: (t: Type) => Type): Type {
@@ -8323,10 +8398,10 @@ namespace ts {
83238398
if (isMatchingReference(reference, expr)) {
83248399
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
83258400
}
8326-
if (isMatchingPropertyAccess(expr)) {
8401+
if (isMatchingReferenceDiscriminant(expr)) {
83278402
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
83288403
}
8329-
if (rootContainsMatchingReference(reference, expr)) {
8404+
if (containsMatchingReferenceDiscriminant(reference, expr)) {
83308405
return declaredType;
83318406
}
83328407
return type;
@@ -8355,13 +8430,13 @@ namespace ts {
83558430
if (isMatchingReference(reference, right)) {
83568431
return narrowTypeByEquality(type, operator, left, assumeTrue);
83578432
}
8358-
if (isMatchingPropertyAccess(left)) {
8433+
if (isMatchingReferenceDiscriminant(left)) {
83598434
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
83608435
}
8361-
if (isMatchingPropertyAccess(right)) {
8436+
if (isMatchingReferenceDiscriminant(right)) {
83628437
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
83638438
}
8364-
if (rootContainsMatchingReference(reference, left) || rootContainsMatchingReference(reference, right)) {
8439+
if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) {
83658440
return declaredType;
83668441
}
83678442
break;
@@ -8500,9 +8575,7 @@ namespace ts {
85008575

85018576
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean) {
85028577
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));
85068579
}
85078580
// If the current type is a union type, remove all constituents that aren't assignable to
85088581
// the candidate type. If one or more constituents remain, return a union of those.
@@ -8512,13 +8585,16 @@ namespace ts {
85128585
return getUnionType(assignableConstituents);
85138586
}
85148587
}
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.
85188593
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
8519-
return isTypeAssignableTo(candidate, targetType) ? candidate :
8594+
return isTypeSubtypeOf(candidate, targetType) ? candidate :
85208595
isTypeAssignableTo(type, candidate) ? type :
8521-
getIntersectionType([type, candidate]);
8596+
isTypeAssignableTo(candidate, targetType) ? candidate :
8597+
getIntersectionType([type, candidate]);
85228598
}
85238599

85248600
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2160,6 +2160,8 @@ namespace ts {
21602160
mapper?: TypeMapper; // Type mapper for instantiation alias
21612161
referenced?: boolean; // True if alias symbol has been referenced as a value
21622162
containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property
2163+
hasCommonType?: boolean; // True if constituents of synthetic property all have same type
2164+
isDiscriminantProperty?: boolean; // True if discriminant synthetic property
21632165
resolvedExports?: SymbolTable; // Resolved exports of module
21642166
exportsChecked?: boolean; // True if exports of external module have been checked
21652167
isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration

tests/baselines/reference/controlFlowBinaryOrExpression.symbols

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) {
8686
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
8787

8888
sourceObj.length;
89-
>sourceObj.length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
89+
>sourceObj.length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
9090
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
91-
>length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
91+
>length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
9292
}
9393

tests/baselines/reference/controlFlowBinaryOrExpression.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) {
106106

107107
sourceObj.length;
108108
>sourceObj.length : number
109-
>sourceObj : NodeList | HTMLCollection | ({ a: string; } & HTMLCollection)
109+
>sourceObj : NodeList
110110
>length : number
111111
}
112112

0 commit comments

Comments
 (0)