Merge pull request #8962 from Microsoft/propertyAccessNarrowing

Fix narrowing of property access expressions
This commit is contained in:
Anders Hejlsberg
2016-06-03 18:13:55 -07:00
6 changed files with 134 additions and 25 deletions
+5 -15
View File
@@ -9991,24 +9991,14 @@ namespace ts {
}
const propType = getTypeOfSymbol(prop);
// Only compute control flow type if this is a property access expression that isn't an
// assignment target, and the referenced property was declared as a variable, property,
// accessor, or optional method.
if (node.kind !== SyntaxKind.PropertyAccessExpression || isAssignmentTarget(node) ||
!(propType.flags & TypeFlags.Union) && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor))) {
!(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) &&
!(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
return propType;
}
const leftmostNode = getLeftmostIdentifierOrThis(node);
if (!leftmostNode) {
return propType;
}
if (leftmostNode.kind === SyntaxKind.Identifier) {
const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>leftmostNode));
if (!leftmostSymbol) {
return propType;
}
const declaration = leftmostSymbol.valueDeclaration;
if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) {
return propType;
}
}
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false);
}
@@ -0,0 +1,32 @@
//// [classStaticPropertyTypeGuard.ts]
// Repro from #8923
class A {
private static _a: string | undefined;
public get a(): string {
if (A._a) {
return A._a; // is possibly null or undefined.
}
return A._a = 'helloworld';
}
}
//// [classStaticPropertyTypeGuard.js]
// Repro from #8923
var A = (function () {
function A() {
}
Object.defineProperty(A.prototype, "a", {
get: function () {
if (A._a) {
return A._a; // is possibly null or undefined.
}
return A._a = 'helloworld';
},
enumerable: true,
configurable: true
});
return A;
}());
@@ -0,0 +1,29 @@
=== tests/cases/compiler/classStaticPropertyTypeGuard.ts ===
// Repro from #8923
class A {
>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0))
private static _a: string | undefined;
>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
public get a(): string {
>a : Symbol(A.a, Decl(classStaticPropertyTypeGuard.ts, 4, 42))
if (A._a) {
>A._a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0))
>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
return A._a; // is possibly null or undefined.
>A._a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0))
>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
}
return A._a = 'helloworld';
>A._a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0))
>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
}
}
@@ -0,0 +1,31 @@
=== tests/cases/compiler/classStaticPropertyTypeGuard.ts ===
// Repro from #8923
class A {
>A : A
private static _a: string | undefined;
>_a : string | undefined
public get a(): string {
>a : string
if (A._a) {
>A._a : string | undefined
>A : typeof A
>_a : string | undefined
return A._a; // is possibly null or undefined.
>A._a : string
>A : typeof A
>_a : string
}
return A._a = 'helloworld';
>A._a = 'helloworld' : string
>A._a : string | undefined
>A : typeof A
>_a : string | undefined
>'helloworld' : string
}
}
@@ -0,0 +1,15 @@
// @strictNullChecks: true
// @target: ES5
// Repro from #8923
class A {
private static _a: string | undefined;
public get a(): string {
if (A._a) {
return A._a; // is possibly null or undefined.
}
return A._a = 'helloworld';
}
}
@@ -50,14 +50,26 @@ goTo.marker('6');
verify.quickInfoIs('var m.exportedStrOrNum: string');
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string");
['7', '8', '9'].forEach((marker, index, arr) => {
goTo.marker(marker);
verify.quickInfoIs('var m.exportedStrOrNum: string | number');
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number");
});
goTo.marker('7');
verify.quickInfoIs('var m.exportedStrOrNum: string | number');
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number");
['7', '8', '9'].forEach((marker, index, arr) => {
goTo.marker(marker);
verify.quickInfoIs('var m.exportedStrOrNum: string | number');
verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number");
});
goTo.marker('8');
verify.quickInfoIs('var m.exportedStrOrNum: number');
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: number");
goTo.marker('9');
verify.quickInfoIs('var m.exportedStrOrNum: string');
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string");
goTo.marker('7');
verify.quickInfoIs('var m.exportedStrOrNum: string | number');
verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number");
goTo.marker('8');
verify.quickInfoIs('var m.exportedStrOrNum: number');
verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: number");
goTo.marker('9');
verify.quickInfoIs('var m.exportedStrOrNum: string');
verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: string");