Skip to content

Commit 36b6113

Browse files
authored
Merge pull request #10028 from Microsoft/fixDiscriminantInLoop
Fix discriminant in loop
2 parents 4f27f65 + 0a90a4b commit 36b6113

File tree

5 files changed

+753
-21
lines changed

5 files changed

+753
-21
lines changed

src/compiler/checker.ts

+28-21
Original file line numberDiff line numberDiff line change
@@ -7764,16 +7764,17 @@ namespace ts {
77647764
}
77657765

77667766
function isMatchingReference(source: Node, target: Node): boolean {
7767-
if (source.kind === target.kind) {
7768-
switch (source.kind) {
7769-
case SyntaxKind.Identifier:
7770-
return getResolvedSymbol(<Identifier>source) === getResolvedSymbol(<Identifier>target);
7771-
case SyntaxKind.ThisKeyword:
7772-
return true;
7773-
case SyntaxKind.PropertyAccessExpression:
7774-
return (<PropertyAccessExpression>source).name.text === (<PropertyAccessExpression>target).name.text &&
7775-
isMatchingReference((<PropertyAccessExpression>source).expression, (<PropertyAccessExpression>target).expression);
7776-
}
7767+
switch (source.kind) {
7768+
case SyntaxKind.Identifier:
7769+
return target.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>source) === getResolvedSymbol(<Identifier>target) ||
7770+
(target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) &&
7771+
getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>source)) === getSymbolOfNode(target);
7772+
case SyntaxKind.ThisKeyword:
7773+
return target.kind === SyntaxKind.ThisKeyword;
7774+
case SyntaxKind.PropertyAccessExpression:
7775+
return target.kind === SyntaxKind.PropertyAccessExpression &&
7776+
(<PropertyAccessExpression>source).name.text === (<PropertyAccessExpression>target).name.text &&
7777+
isMatchingReference((<PropertyAccessExpression>source).expression, (<PropertyAccessExpression>target).expression);
77777778
}
77787779
return false;
77797780
}
@@ -7788,6 +7789,10 @@ namespace ts {
77887789
return false;
77897790
}
77907791

7792+
function rootContainsMatchingReference(source: Node, target: Node) {
7793+
return target.kind === SyntaxKind.PropertyAccessExpression && containsMatchingReference(source, (<PropertyAccessExpression>target).expression);
7794+
}
7795+
77917796
function isOrContainsMatchingReference(source: Node, target: Node) {
77927797
return isMatchingReference(source, target) || containsMatchingReference(source, target);
77937798
}
@@ -8031,6 +8036,12 @@ namespace ts {
80318036
getInitialTypeOfBindingElement(<BindingElement>node);
80328037
}
80338038

8039+
function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression) {
8040+
return node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
8041+
getInitialType(<VariableDeclaration | BindingElement>node) :
8042+
getAssignedType(<Expression>node);
8043+
}
8044+
80348045
function getReferenceCandidate(node: Expression): Expression {
80358046
switch (node.kind) {
80368047
case SyntaxKind.ParenthesizedExpression:
@@ -8153,19 +8164,9 @@ namespace ts {
81538164
const node = flow.node;
81548165
// Assignments only narrow the computed type if the declared type is a union type. Thus, we
81558166
// only need to evaluate the assigned type if the declared type is a union type.
8156-
if ((node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) &&
8157-
reference.kind === SyntaxKind.Identifier &&
8158-
getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>reference)) === getSymbolOfNode(node)) {
8159-
return declaredType.flags & TypeFlags.Union ?
8160-
getAssignmentReducedType(<UnionType>declaredType, getInitialType(<VariableDeclaration | BindingElement>node)) :
8161-
declaredType;
8162-
}
8163-
// If the node is not a variable declaration or binding element, it is an identifier
8164-
// or a dotted name that is the target of an assignment. If we have a match, reduce
8165-
// the declared type by the assigned type.
81668167
if (isMatchingReference(reference, node)) {
81678168
return declaredType.flags & TypeFlags.Union ?
8168-
getAssignmentReducedType(<UnionType>declaredType, getAssignedType(<Expression>node)) :
8169+
getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(node)) :
81698170
declaredType;
81708171
}
81718172
// We didn't have a direct match. However, if the reference is a dotted name, this
@@ -8297,6 +8298,9 @@ namespace ts {
82978298
if (isMatchingPropertyAccess(expr)) {
82988299
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
82998300
}
8301+
if (rootContainsMatchingReference(reference, expr)) {
8302+
return declaredType;
8303+
}
83008304
return type;
83018305
}
83028306

@@ -8329,6 +8333,9 @@ namespace ts {
83298333
if (isMatchingPropertyAccess(right)) {
83308334
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
83318335
}
8336+
if (rootContainsMatchingReference(reference, left) || rootContainsMatchingReference(reference, right)) {
8337+
return declaredType;
8338+
}
83328339
break;
83338340
case SyntaxKind.InstanceOfKeyword:
83348341
return narrowTypeByInstanceof(type, expr, assumeTrue);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//// [narrowingByDiscriminantInLoop.ts]
2+
3+
// Repro from #9977
4+
5+
type IDLMemberTypes = OperationMemberType | ConstantMemberType;
6+
7+
interface IDLTypeDescription {
8+
origin: string;
9+
}
10+
11+
interface InterfaceType {
12+
members: IDLMemberTypes[];
13+
}
14+
15+
interface OperationMemberType {
16+
type: "operation";
17+
idlType: IDLTypeDescription;
18+
}
19+
20+
interface ConstantMemberType {
21+
type: "const";
22+
idlType: string;
23+
}
24+
25+
function insertInterface(callbackType: InterfaceType) {
26+
for (const memberType of callbackType.members) {
27+
if (memberType.type === "const") {
28+
memberType.idlType; // string
29+
}
30+
else if (memberType.type === "operation") {
31+
memberType.idlType.origin; // string
32+
(memberType.idlType as IDLTypeDescription);
33+
}
34+
}
35+
}
36+
37+
function insertInterface2(callbackType: InterfaceType) {
38+
for (const memberType of callbackType.members) {
39+
if (memberType.type === "operation") {
40+
memberType.idlType.origin; // string
41+
}
42+
}
43+
}
44+
45+
function foo(memberType: IDLMemberTypes) {
46+
if (memberType.type === "const") {
47+
memberType.idlType; // string
48+
}
49+
else if (memberType.type === "operation") {
50+
memberType.idlType.origin; // string
51+
}
52+
}
53+
54+
// Repro for issue similar to #8383
55+
56+
interface A {
57+
kind: true;
58+
prop: { a: string; };
59+
}
60+
61+
interface B {
62+
kind: false;
63+
prop: { b: string; }
64+
}
65+
66+
function f1(x: A | B) {
67+
while (true) {
68+
x.prop;
69+
if (x.kind === true) {
70+
x.prop.a;
71+
}
72+
if (x.kind === false) {
73+
x.prop.b;
74+
}
75+
}
76+
}
77+
78+
function f2(x: A | B) {
79+
while (true) {
80+
if (x.kind) {
81+
x.prop.a;
82+
}
83+
if (!x.kind) {
84+
x.prop.b;
85+
}
86+
}
87+
}
88+
89+
//// [narrowingByDiscriminantInLoop.js]
90+
// Repro from #9977
91+
function insertInterface(callbackType) {
92+
for (var _i = 0, _a = callbackType.members; _i < _a.length; _i++) {
93+
var memberType = _a[_i];
94+
if (memberType.type === "const") {
95+
memberType.idlType; // string
96+
}
97+
else if (memberType.type === "operation") {
98+
memberType.idlType.origin; // string
99+
memberType.idlType;
100+
}
101+
}
102+
}
103+
function insertInterface2(callbackType) {
104+
for (var _i = 0, _a = callbackType.members; _i < _a.length; _i++) {
105+
var memberType = _a[_i];
106+
if (memberType.type === "operation") {
107+
memberType.idlType.origin; // string
108+
}
109+
}
110+
}
111+
function foo(memberType) {
112+
if (memberType.type === "const") {
113+
memberType.idlType; // string
114+
}
115+
else if (memberType.type === "operation") {
116+
memberType.idlType.origin; // string
117+
}
118+
}
119+
function f1(x) {
120+
while (true) {
121+
x.prop;
122+
if (x.kind === true) {
123+
x.prop.a;
124+
}
125+
if (x.kind === false) {
126+
x.prop.b;
127+
}
128+
}
129+
}
130+
function f2(x) {
131+
while (true) {
132+
if (x.kind) {
133+
x.prop.a;
134+
}
135+
if (!x.kind) {
136+
x.prop.b;
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)