Skip to content

Commit f0e2d81

Browse files
committed
Merge pull request #8390 from Microsoft/narrowingOfDottedNames
Fix narrowing of dotted names
2 parents 8acc885 + ef51195 commit f0e2d81

File tree

5 files changed

+380
-19
lines changed

5 files changed

+380
-19
lines changed

src/compiler/checker.ts

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7335,27 +7335,29 @@ namespace ts {
73357335
}
73367336

73377337
function containsMatchingReference(source: Node, target: Node) {
7338-
while (true) {
7338+
while (source.kind === SyntaxKind.PropertyAccessExpression) {
7339+
source = (<PropertyAccessExpression>source).expression;
73397340
if (isMatchingReference(source, target)) {
73407341
return true;
73417342
}
7342-
if (source.kind !== SyntaxKind.PropertyAccessExpression) {
7343-
return false;
7344-
}
7345-
source = (<PropertyAccessExpression>source).expression;
73467343
}
7344+
return false;
7345+
}
7346+
7347+
function isOrContainsMatchingReference(source: Node, target: Node) {
7348+
return isMatchingReference(source, target) || containsMatchingReference(source, target);
73477349
}
73487350

7349-
function hasMatchingArgument(callExpression: CallExpression, target: Node) {
7351+
function hasMatchingArgument(callExpression: CallExpression, reference: Node) {
73507352
if (callExpression.arguments) {
73517353
for (const argument of callExpression.arguments) {
7352-
if (isMatchingReference(argument, target)) {
7354+
if (isOrContainsMatchingReference(reference, argument)) {
73537355
return true;
73547356
}
73557357
}
73567358
}
73577359
if (callExpression.expression.kind === SyntaxKind.PropertyAccessExpression &&
7358-
isMatchingReference((<PropertyAccessExpression>callExpression.expression).expression, target)) {
7360+
isOrContainsMatchingReference(reference, (<PropertyAccessExpression>callExpression.expression).expression)) {
73597361
return true;
73607362
}
73617363
return false;
@@ -7618,8 +7620,7 @@ namespace ts {
76187620
// may be an assignment to a left hand part of the reference. For example, for a
76197621
// reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case,
76207622
// return the declared type.
7621-
if (reference.kind === SyntaxKind.PropertyAccessExpression &&
7622-
containsMatchingReference((<PropertyAccessExpression>reference).expression, node)) {
7623+
if (containsMatchingReference(reference, node)) {
76237624
return declaredType;
76247625
}
76257626
// Assignment doesn't affect reference
@@ -7682,7 +7683,7 @@ namespace ts {
76827683
}
76837684

76847685
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
7685-
return isMatchingReference(expr, reference) ? getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy) : type;
7686+
return isMatchingReference(reference, expr) ? getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy) : type;
76867687
}
76877688

76887689
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
@@ -7714,7 +7715,7 @@ namespace ts {
77147715
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
77157716
assumeTrue = !assumeTrue;
77167717
}
7717-
if (!strictNullChecks || !isMatchingReference(expr.left, reference)) {
7718+
if (!strictNullChecks || !isMatchingReference(reference, expr.left)) {
77187719
return type;
77197720
}
77207721
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
@@ -7731,7 +7732,12 @@ namespace ts {
77317732
// and string literal on the right
77327733
const left = <TypeOfExpression>expr.left;
77337734
const right = <LiteralExpression>expr.right;
7734-
if (!isMatchingReference(left.expression, reference)) {
7735+
if (!isMatchingReference(reference, left.expression)) {
7736+
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
7737+
// narrowed type of 'y' to its declared type.
7738+
if (containsMatchingReference(reference, left.expression)) {
7739+
return declaredType;
7740+
}
77357741
return type;
77367742
}
77377743
if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsToken ||
@@ -7754,8 +7760,16 @@ namespace ts {
77547760
}
77557761

77567762
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
7757-
// Check that type is not any, assumed result is true, and we have variable symbol on the left
7758-
if (isTypeAny(type) || !isMatchingReference(expr.left, reference)) {
7763+
if (!isMatchingReference(reference, expr.left)) {
7764+
// For a reference of the form 'x.y', an 'x instanceof T' type guard resets the
7765+
// narrowed type of 'y' to its declared type.
7766+
if (containsMatchingReference(reference, expr.left)) {
7767+
return declaredType;
7768+
}
7769+
return type;
7770+
}
7771+
// We never narrow type any in an instanceof guard
7772+
if (isTypeAny(type)) {
77597773
return type;
77607774
}
77617775

@@ -7830,18 +7844,26 @@ namespace ts {
78307844
}
78317845
if (isIdentifierTypePredicate(predicate)) {
78327846
const predicateArgument = callExpression.arguments[predicate.parameterIndex];
7833-
if (predicateArgument && isMatchingReference(predicateArgument, reference)) {
7834-
return getNarrowedType(type, predicate.type, assumeTrue);
7847+
if (predicateArgument) {
7848+
if (isMatchingReference(reference, predicateArgument)) {
7849+
return getNarrowedType(type, predicate.type, assumeTrue);
7850+
}
7851+
if (containsMatchingReference(reference, predicateArgument)) {
7852+
return declaredType;
7853+
}
78357854
}
78367855
}
78377856
else {
78387857
const invokedExpression = skipParenthesizedNodes(callExpression.expression);
78397858
if (invokedExpression.kind === SyntaxKind.ElementAccessExpression || invokedExpression.kind === SyntaxKind.PropertyAccessExpression) {
78407859
const accessExpression = invokedExpression as ElementAccessExpression | PropertyAccessExpression;
7841-
const possibleReference= skipParenthesizedNodes(accessExpression.expression);
7842-
if (isMatchingReference(possibleReference, reference)) {
7860+
const possibleReference = skipParenthesizedNodes(accessExpression.expression);
7861+
if (isMatchingReference(reference, possibleReference)) {
78437862
return getNarrowedType(type, predicate.type, assumeTrue);
78447863
}
7864+
if (containsMatchingReference(reference, possibleReference)) {
7865+
return declaredType;
7866+
}
78457867
}
78467868
}
78477869
return type;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//// [narrowingOfDottedNames.ts]
2+
// Repro from #8383
3+
4+
class A {
5+
prop: { a: string; };
6+
}
7+
8+
class B {
9+
prop: { b: string; }
10+
}
11+
12+
function isA(x: any): x is A {
13+
return x instanceof A;
14+
}
15+
16+
function isB(x: any): x is B {
17+
return x instanceof B;
18+
}
19+
20+
function f1(x: A | B) {
21+
while (true) {
22+
if (x instanceof A) {
23+
x.prop.a;
24+
}
25+
else if (x instanceof B) {
26+
x.prop.b;
27+
}
28+
}
29+
}
30+
31+
function f2(x: A | B) {
32+
while (true) {
33+
if (isA(x)) {
34+
x.prop.a;
35+
}
36+
else if (isB(x)) {
37+
x.prop.b;
38+
}
39+
}
40+
}
41+
42+
43+
//// [narrowingOfDottedNames.js]
44+
// Repro from #8383
45+
var A = (function () {
46+
function A() {
47+
}
48+
return A;
49+
}());
50+
var B = (function () {
51+
function B() {
52+
}
53+
return B;
54+
}());
55+
function isA(x) {
56+
return x instanceof A;
57+
}
58+
function isB(x) {
59+
return x instanceof B;
60+
}
61+
function f1(x) {
62+
while (true) {
63+
if (x instanceof A) {
64+
x.prop.a;
65+
}
66+
else if (x instanceof B) {
67+
x.prop.b;
68+
}
69+
}
70+
}
71+
function f2(x) {
72+
while (true) {
73+
if (isA(x)) {
74+
x.prop.a;
75+
}
76+
else if (isB(x)) {
77+
x.prop.b;
78+
}
79+
}
80+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
=== tests/cases/compiler/narrowingOfDottedNames.ts ===
2+
// Repro from #8383
3+
4+
class A {
5+
>A : Symbol(A, Decl(narrowingOfDottedNames.ts, 0, 0))
6+
7+
prop: { a: string; };
8+
>prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
9+
>a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
10+
}
11+
12+
class B {
13+
>B : Symbol(B, Decl(narrowingOfDottedNames.ts, 4, 1))
14+
15+
prop: { b: string; }
16+
>prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
17+
>b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
18+
}
19+
20+
function isA(x: any): x is A {
21+
>isA : Symbol(isA, Decl(narrowingOfDottedNames.ts, 8, 1))
22+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 10, 13))
23+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 10, 13))
24+
>A : Symbol(A, Decl(narrowingOfDottedNames.ts, 0, 0))
25+
26+
return x instanceof A;
27+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 10, 13))
28+
>A : Symbol(A, Decl(narrowingOfDottedNames.ts, 0, 0))
29+
}
30+
31+
function isB(x: any): x is B {
32+
>isB : Symbol(isB, Decl(narrowingOfDottedNames.ts, 12, 1))
33+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 14, 13))
34+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 14, 13))
35+
>B : Symbol(B, Decl(narrowingOfDottedNames.ts, 4, 1))
36+
37+
return x instanceof B;
38+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 14, 13))
39+
>B : Symbol(B, Decl(narrowingOfDottedNames.ts, 4, 1))
40+
}
41+
42+
function f1(x: A | B) {
43+
>f1 : Symbol(f1, Decl(narrowingOfDottedNames.ts, 16, 1))
44+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 18, 12))
45+
>A : Symbol(A, Decl(narrowingOfDottedNames.ts, 0, 0))
46+
>B : Symbol(B, Decl(narrowingOfDottedNames.ts, 4, 1))
47+
48+
while (true) {
49+
if (x instanceof A) {
50+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 18, 12))
51+
>A : Symbol(A, Decl(narrowingOfDottedNames.ts, 0, 0))
52+
53+
x.prop.a;
54+
>x.prop.a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
55+
>x.prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
56+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 18, 12))
57+
>prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
58+
>a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
59+
}
60+
else if (x instanceof B) {
61+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 18, 12))
62+
>B : Symbol(B, Decl(narrowingOfDottedNames.ts, 4, 1))
63+
64+
x.prop.b;
65+
>x.prop.b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
66+
>x.prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
67+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 18, 12))
68+
>prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
69+
>b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
70+
}
71+
}
72+
}
73+
74+
function f2(x: A | B) {
75+
>f2 : Symbol(f2, Decl(narrowingOfDottedNames.ts, 27, 1))
76+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 29, 12))
77+
>A : Symbol(A, Decl(narrowingOfDottedNames.ts, 0, 0))
78+
>B : Symbol(B, Decl(narrowingOfDottedNames.ts, 4, 1))
79+
80+
while (true) {
81+
if (isA(x)) {
82+
>isA : Symbol(isA, Decl(narrowingOfDottedNames.ts, 8, 1))
83+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 29, 12))
84+
85+
x.prop.a;
86+
>x.prop.a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
87+
>x.prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
88+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 29, 12))
89+
>prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
90+
>a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
91+
}
92+
else if (isB(x)) {
93+
>isB : Symbol(isB, Decl(narrowingOfDottedNames.ts, 12, 1))
94+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 29, 12))
95+
96+
x.prop.b;
97+
>x.prop.b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
98+
>x.prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
99+
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 29, 12))
100+
>prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
101+
>b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
102+
}
103+
}
104+
}
105+

0 commit comments

Comments
 (0)