Stricter check for discriminant properties in type guards

This commit is contained in:
Anders Hejlsberg
2016-08-06 09:01:35 -07:00
parent b54aec1c12
commit 37e96b3a06
2 changed files with 54 additions and 10 deletions
+52 -10
View File
@@ -4416,10 +4416,19 @@ namespace ts {
}
const propTypes: Type[] = [];
const declarations: Declaration[] = [];
let commonType: Type = undefined;
let hasCommonType = true;
for (const prop of props) {
if (prop.declarations) {
addRange(declarations, prop.declarations);
}
const type = getTypeOfSymbol(prop);
if (!commonType) {
commonType = type;
}
else if (type !== commonType) {
hasCommonType = false;
}
propTypes.push(getTypeOfSymbol(prop));
}
const result = <TransientSymbol>createSymbol(
@@ -4429,6 +4438,7 @@ namespace ts {
commonFlags,
name);
result.containingType = containingType;
result.hasCommonType = hasCommonType;
result.declarations = declarations;
result.isReadonly = isReadonly;
result.type = containingType.flags & TypeFlags.Union ? getUnionType(propTypes) : getIntersectionType(propTypes);
@@ -7793,8 +7803,39 @@ namespace ts {
return false;
}
function rootContainsMatchingReference(source: Node, target: Node) {
return target.kind === SyntaxKind.PropertyAccessExpression && containsMatchingReference(source, (<PropertyAccessExpression>target).expression);
// Return true if target is a property access xxx.yyy, source is a property access xxx.zzz, the declared
// type of xxx is a union type, and yyy is a property that is possibly a discriminant. We consider a property
// a possible discriminant if its type differs in the constituents of containing union type, and if every
// choice is a unit type or a union of unit types.
function containsMatchingReferenceDiscriminant(source: Node, target: Node) {
return target.kind === SyntaxKind.PropertyAccessExpression &&
containsMatchingReference(source, (<PropertyAccessExpression>target).expression) &&
isDiscriminantProperty(getDeclaredTypeOfReference((<PropertyAccessExpression>target).expression), (<PropertyAccessExpression>target).name.text);
}
function getDeclaredTypeOfReference(expr: Node): Type {
if (expr.kind === SyntaxKind.Identifier) {
return getTypeOfSymbol(getResolvedSymbol(<Identifier>expr));
}
if (expr.kind === SyntaxKind.PropertyAccessExpression) {
const type = getDeclaredTypeOfReference((<PropertyAccessExpression>expr).expression);
return type && getTypeOfPropertyOfType(type, (<PropertyAccessExpression>expr).name.text);
}
return undefined;
}
function isDiscriminantProperty(type: Type, name: string) {
if (type && type.flags & TypeFlags.Union) {
const prop = getPropertyOfType(type, name);
if (prop && prop.flags & SymbolFlags.SyntheticProperty) {
if ((<TransientSymbol>prop).isDiscriminantProperty === undefined) {
(<TransientSymbol>prop).isDiscriminantProperty = !(<TransientSymbol>prop).hasCommonType &&
isUnitUnionType(getTypeOfSymbol(prop));
}
return (<TransientSymbol>prop).isDiscriminantProperty;
}
}
return false;
}
function isOrContainsMatchingReference(source: Node, target: Node) {
@@ -8222,7 +8263,7 @@ namespace ts {
if (isMatchingReference(reference, expr)) {
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
}
else if (isMatchingPropertyAccess(expr)) {
else if (isMatchingReferenceDiscriminant(expr)) {
type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
}
return createFlowType(type, isIncomplete(flowType));
@@ -8300,10 +8341,11 @@ namespace ts {
return cache[key] = getUnionType(antecedentTypes);
}
function isMatchingPropertyAccess(expr: Expression) {
function isMatchingReferenceDiscriminant(expr: Expression) {
return expr.kind === SyntaxKind.PropertyAccessExpression &&
declaredType.flags & TypeFlags.Union &&
isMatchingReference(reference, (<PropertyAccessExpression>expr).expression) &&
(declaredType.flags & TypeFlags.Union) !== 0;
isDiscriminantProperty(declaredType, (<PropertyAccessExpression>expr).name.text);
}
function narrowTypeByDiscriminant(type: Type, propAccess: PropertyAccessExpression, narrowType: (t: Type) => Type): Type {
@@ -8317,10 +8359,10 @@ namespace ts {
if (isMatchingReference(reference, expr)) {
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
}
if (isMatchingPropertyAccess(expr)) {
if (isMatchingReferenceDiscriminant(expr)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
}
if (rootContainsMatchingReference(reference, expr)) {
if (containsMatchingReferenceDiscriminant(reference, expr)) {
return declaredType;
}
return type;
@@ -8349,13 +8391,13 @@ namespace ts {
if (isMatchingReference(reference, right)) {
return narrowTypeByEquality(type, operator, left, assumeTrue);
}
if (isMatchingPropertyAccess(left)) {
if (isMatchingReferenceDiscriminant(left)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
}
if (isMatchingPropertyAccess(right)) {
if (isMatchingReferenceDiscriminant(right)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
}
if (rootContainsMatchingReference(reference, left) || rootContainsMatchingReference(reference, right)) {
if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) {
return declaredType;
}
break;
+2
View File
@@ -2160,6 +2160,8 @@ namespace ts {
mapper?: TypeMapper; // Type mapper for instantiation alias
referenced?: boolean; // True if alias symbol has been referenced as a value
containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property
hasCommonType?: boolean; // True if constituents of synthetic property all have same type
isDiscriminantProperty?: boolean; // True if discriminant synthetic property
resolvedExports?: SymbolTable; // Resolved exports of module
exportsChecked?: boolean; // True if exports of external module have been checked
isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration