Skip to content

Commit 6dcd587

Browse files
committed
Merge pull request microsoft#8962 from Microsoft/propertyAccessNarrowing
Fix narrowing of property access expressions
2 parents 3aaa4ea + 87ee72b commit 6dcd587

6 files changed

Lines changed: 135 additions & 26 deletions

File tree

src/compiler/checker.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9991,24 +9991,14 @@ namespace ts {
99919991
}
99929992

99939993
const propType = getTypeOfSymbol(prop);
9994+
// Only compute control flow type if this is a property access expression that isn't an
9995+
// assignment target, and the referenced property was declared as a variable, property,
9996+
// accessor, or optional method.
99949997
if (node.kind !== SyntaxKind.PropertyAccessExpression || isAssignmentTarget(node) ||
9995-
!(propType.flags & TypeFlags.Union) && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor))) {
9998+
!(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) &&
9999+
!(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
999610000
return propType;
999710001
}
9998-
const leftmostNode = getLeftmostIdentifierOrThis(node);
9999-
if (!leftmostNode) {
10000-
return propType;
10001-
}
10002-
if (leftmostNode.kind === SyntaxKind.Identifier) {
10003-
const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>leftmostNode));
10004-
if (!leftmostSymbol) {
10005-
return propType;
10006-
}
10007-
const declaration = leftmostSymbol.valueDeclaration;
10008-
if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) {
10009-
return propType;
10010-
}
10011-
}
1001210002
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false);
1001310003
}
1001410004

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [classStaticPropertyTypeGuard.ts]
2+
3+
// Repro from #8923
4+
5+
class A {
6+
private static _a: string | undefined;
7+
8+
public get a(): string {
9+
if (A._a) {
10+
return A._a; // is possibly null or undefined.
11+
}
12+
return A._a = 'helloworld';
13+
}
14+
}
15+
16+
//// [classStaticPropertyTypeGuard.js]
17+
// Repro from #8923
18+
var A = (function () {
19+
function A() {
20+
}
21+
Object.defineProperty(A.prototype, "a", {
22+
get: function () {
23+
if (A._a) {
24+
return A._a; // is possibly null or undefined.
25+
}
26+
return A._a = 'helloworld';
27+
},
28+
enumerable: true,
29+
configurable: true
30+
});
31+
return A;
32+
}());
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/compiler/classStaticPropertyTypeGuard.ts ===
2+
3+
// Repro from #8923
4+
5+
class A {
6+
>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0))
7+
8+
private static _a: string | undefined;
9+
>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
10+
11+
public get a(): string {
12+
>a : Symbol(A.a, Decl(classStaticPropertyTypeGuard.ts, 4, 42))
13+
14+
if (A._a) {
15+
>A._a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
16+
>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0))
17+
>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
18+
19+
return A._a; // is possibly null or undefined.
20+
>A._a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
21+
>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0))
22+
>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
23+
}
24+
return A._a = 'helloworld';
25+
>A._a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
26+
>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0))
27+
>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9))
28+
}
29+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== tests/cases/compiler/classStaticPropertyTypeGuard.ts ===
2+
3+
// Repro from #8923
4+
5+
class A {
6+
>A : A
7+
8+
private static _a: string | undefined;
9+
>_a : string | undefined
10+
11+
public get a(): string {
12+
>a : string
13+
14+
if (A._a) {
15+
>A._a : string | undefined
16+
>A : typeof A
17+
>_a : string | undefined
18+
19+
return A._a; // is possibly null or undefined.
20+
>A._a : string
21+
>A : typeof A
22+
>_a : string
23+
}
24+
return A._a = 'helloworld';
25+
>A._a = 'helloworld' : string
26+
>A._a : string | undefined
27+
>A : typeof A
28+
>_a : string | undefined
29+
>'helloworld' : string
30+
}
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @strictNullChecks: true
2+
// @target: ES5
3+
4+
// Repro from #8923
5+
6+
class A {
7+
private static _a: string | undefined;
8+
9+
public get a(): string {
10+
if (A._a) {
11+
return A._a; // is possibly null or undefined.
12+
}
13+
return A._a = 'helloworld';
14+
}
15+
}

tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,26 @@ goTo.marker('6');
5050
verify.quickInfoIs('var m.exportedStrOrNum: string');
5151
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string");
5252

53-
['7', '8', '9'].forEach((marker, index, arr) => {
54-
goTo.marker(marker);
55-
verify.quickInfoIs('var m.exportedStrOrNum: string | number');
56-
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number");
57-
});
58-
59-
['7', '8', '9'].forEach((marker, index, arr) => {
60-
goTo.marker(marker);
61-
verify.quickInfoIs('var m.exportedStrOrNum: string | number');
62-
verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number");
63-
});
53+
goTo.marker('7');
54+
verify.quickInfoIs('var m.exportedStrOrNum: string | number');
55+
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number");
56+
57+
goTo.marker('8');
58+
verify.quickInfoIs('var m.exportedStrOrNum: number');
59+
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: number");
60+
61+
goTo.marker('9');
62+
verify.quickInfoIs('var m.exportedStrOrNum: string');
63+
verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string");
64+
65+
goTo.marker('7');
66+
verify.quickInfoIs('var m.exportedStrOrNum: string | number');
67+
verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number");
68+
69+
goTo.marker('8');
70+
verify.quickInfoIs('var m.exportedStrOrNum: number');
71+
verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: number");
72+
73+
goTo.marker('9');
74+
verify.quickInfoIs('var m.exportedStrOrNum: string');
75+
verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: string");

0 commit comments

Comments
 (0)