Skip to content

Commit a5796cf

Browse files
authored
Remove ordering restrictions in control flow analysis (#37134)
* Don't reset CFA type for x.y when x is narrowed * Add tests * Accept new baselines * Remove unnecessary type assertion
1 parent f914db0 commit a5796cf

File tree

6 files changed

+279
-24
lines changed

6 files changed

+279
-24
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3238,7 +3238,7 @@ namespace ts {
32383238
// If this is a property-parameter, then also declare the property symbol into the
32393239
// containing class.
32403240
if (isParameterPropertyDeclaration(node, node.parent)) {
3241-
const classDeclaration = <ClassLikeDeclaration>node.parent.parent;
3241+
const classDeclaration = node.parent.parent;
32423242
declareSymbol(classDeclaration.symbol.members!, classDeclaration.symbol, node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes);
32433243
}
32443244
}

src/compiler/checker.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18903,10 +18903,6 @@ namespace ts {
1890318903
return false;
1890418904
}
1890518905

18906-
function isSyntheticThisPropertyAccess(expr: Node) {
18907-
return isAccessExpression(expr) && expr.expression.kind === SyntaxKind.ThisKeyword && !!(expr.expression.flags & NodeFlags.Synthesized);
18908-
}
18909-
1891018906
function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined {
1891118907
let result: Symbol[] | undefined;
1891218908
for (const sourceProperty of sourceProperties) {
@@ -19634,7 +19630,7 @@ namespace ts {
1963419630
// on empty arrays are possible without implicit any errors and new element types can be inferred without
1963519631
// type mismatch errors.
1963619632
const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType);
19637-
if (resultType === unreachableNeverType|| reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
19633+
if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
1963819634
return declaredType;
1963919635
}
1964019636
return resultType;
@@ -20241,11 +20237,6 @@ namespace ts {
2024120237
if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) {
2024220238
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2024320239
}
20244-
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
20245-
// narrowed type of 'y' to its declared type.
20246-
if (containsMatchingReference(reference, target)) {
20247-
return declaredType;
20248-
}
2024920240
return type;
2025020241
}
2025120242
if (type.flags & TypeFlags.Any && literal.text === "function") {
@@ -20427,16 +20418,6 @@ namespace ts {
2042720418
if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) {
2042820419
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2042920420
}
20430-
// For a reference of the form 'x.y', an 'x instanceof T' type guard resets the
20431-
// narrowed type of 'y' to its declared type. We do this because preceding 'x.y'
20432-
// references might reference a different 'y' property. However, we make an exception
20433-
// for property accesses where x is a synthetic 'this' expression, indicating that we
20434-
// were called from isPropertyInitializedInConstructor. Without this exception,
20435-
// initializations of 'this' properties that occur before a 'this instanceof XXX'
20436-
// check would not be considered.
20437-
if (containsMatchingReference(reference, left) && !isSyntheticThisPropertyAccess(reference)) {
20438-
return declaredType;
20439-
}
2044020421
return type;
2044120422
}
2044220423

@@ -20517,9 +20498,6 @@ namespace ts {
2051720498
!(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) {
2051820499
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2051920500
}
20520-
if (containsMatchingReference(reference, predicateArgument)) {
20521-
return declaredType;
20522-
}
2052320501
}
2052420502
}
2052520503
return type;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//// [narrowingOrderIndependent.ts]
2+
// Repro from #36709
3+
4+
class A {
5+
constructor(public stringOrUndefined: string | undefined) {}
6+
}
7+
8+
class B {
9+
constructor(public str: string) {}
10+
}
11+
12+
const a = new A("123");
13+
14+
if (a instanceof A && a.stringOrUndefined) {
15+
new B(a.stringOrUndefined)
16+
}
17+
18+
if (a.stringOrUndefined && a instanceof A) {
19+
new B(a.stringOrUndefined)
20+
}
21+
22+
if (a instanceof A) {
23+
if (a.stringOrUndefined) {
24+
new B(a.stringOrUndefined)
25+
}
26+
}
27+
28+
if (a.stringOrUndefined) {
29+
if (a instanceof A) {
30+
new B(a.stringOrUndefined)
31+
}
32+
}
33+
34+
35+
//// [narrowingOrderIndependent.js]
36+
"use strict";
37+
// Repro from #36709
38+
var A = /** @class */ (function () {
39+
function A(stringOrUndefined) {
40+
this.stringOrUndefined = stringOrUndefined;
41+
}
42+
return A;
43+
}());
44+
var B = /** @class */ (function () {
45+
function B(str) {
46+
this.str = str;
47+
}
48+
return B;
49+
}());
50+
var a = new A("123");
51+
if (a instanceof A && a.stringOrUndefined) {
52+
new B(a.stringOrUndefined);
53+
}
54+
if (a.stringOrUndefined && a instanceof A) {
55+
new B(a.stringOrUndefined);
56+
}
57+
if (a instanceof A) {
58+
if (a.stringOrUndefined) {
59+
new B(a.stringOrUndefined);
60+
}
61+
}
62+
if (a.stringOrUndefined) {
63+
if (a instanceof A) {
64+
new B(a.stringOrUndefined);
65+
}
66+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
=== tests/cases/compiler/narrowingOrderIndependent.ts ===
2+
// Repro from #36709
3+
4+
class A {
5+
>A : Symbol(A, Decl(narrowingOrderIndependent.ts, 0, 0))
6+
7+
constructor(public stringOrUndefined: string | undefined) {}
8+
>stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
9+
}
10+
11+
class B {
12+
>B : Symbol(B, Decl(narrowingOrderIndependent.ts, 4, 1))
13+
14+
constructor(public str: string) {}
15+
>str : Symbol(B.str, Decl(narrowingOrderIndependent.ts, 7, 16))
16+
}
17+
18+
const a = new A("123");
19+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
20+
>A : Symbol(A, Decl(narrowingOrderIndependent.ts, 0, 0))
21+
22+
if (a instanceof A && a.stringOrUndefined) {
23+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
24+
>A : Symbol(A, Decl(narrowingOrderIndependent.ts, 0, 0))
25+
>a.stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
26+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
27+
>stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
28+
29+
new B(a.stringOrUndefined)
30+
>B : Symbol(B, Decl(narrowingOrderIndependent.ts, 4, 1))
31+
>a.stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
32+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
33+
>stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
34+
}
35+
36+
if (a.stringOrUndefined && a instanceof A) {
37+
>a.stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
38+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
39+
>stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
40+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
41+
>A : Symbol(A, Decl(narrowingOrderIndependent.ts, 0, 0))
42+
43+
new B(a.stringOrUndefined)
44+
>B : Symbol(B, Decl(narrowingOrderIndependent.ts, 4, 1))
45+
>a.stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
46+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
47+
>stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
48+
}
49+
50+
if (a instanceof A) {
51+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
52+
>A : Symbol(A, Decl(narrowingOrderIndependent.ts, 0, 0))
53+
54+
if (a.stringOrUndefined) {
55+
>a.stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
56+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
57+
>stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
58+
59+
new B(a.stringOrUndefined)
60+
>B : Symbol(B, Decl(narrowingOrderIndependent.ts, 4, 1))
61+
>a.stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
62+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
63+
>stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
64+
}
65+
}
66+
67+
if (a.stringOrUndefined) {
68+
>a.stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
69+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
70+
>stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
71+
72+
if (a instanceof A) {
73+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
74+
>A : Symbol(A, Decl(narrowingOrderIndependent.ts, 0, 0))
75+
76+
new B(a.stringOrUndefined)
77+
>B : Symbol(B, Decl(narrowingOrderIndependent.ts, 4, 1))
78+
>a.stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
79+
>a : Symbol(a, Decl(narrowingOrderIndependent.ts, 10, 5))
80+
>stringOrUndefined : Symbol(A.stringOrUndefined, Decl(narrowingOrderIndependent.ts, 3, 16))
81+
}
82+
}
83+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
=== tests/cases/compiler/narrowingOrderIndependent.ts ===
2+
// Repro from #36709
3+
4+
class A {
5+
>A : A
6+
7+
constructor(public stringOrUndefined: string | undefined) {}
8+
>stringOrUndefined : string | undefined
9+
}
10+
11+
class B {
12+
>B : B
13+
14+
constructor(public str: string) {}
15+
>str : string
16+
}
17+
18+
const a = new A("123");
19+
>a : A
20+
>new A("123") : A
21+
>A : typeof A
22+
>"123" : "123"
23+
24+
if (a instanceof A && a.stringOrUndefined) {
25+
>a instanceof A && a.stringOrUndefined : string | false | undefined
26+
>a instanceof A : boolean
27+
>a : A
28+
>A : typeof A
29+
>a.stringOrUndefined : string | undefined
30+
>a : A
31+
>stringOrUndefined : string | undefined
32+
33+
new B(a.stringOrUndefined)
34+
>new B(a.stringOrUndefined) : B
35+
>B : typeof B
36+
>a.stringOrUndefined : string
37+
>a : A
38+
>stringOrUndefined : string
39+
}
40+
41+
if (a.stringOrUndefined && a instanceof A) {
42+
>a.stringOrUndefined && a instanceof A : boolean | "" | undefined
43+
>a.stringOrUndefined : string | undefined
44+
>a : A
45+
>stringOrUndefined : string | undefined
46+
>a instanceof A : boolean
47+
>a : A
48+
>A : typeof A
49+
50+
new B(a.stringOrUndefined)
51+
>new B(a.stringOrUndefined) : B
52+
>B : typeof B
53+
>a.stringOrUndefined : string
54+
>a : A
55+
>stringOrUndefined : string
56+
}
57+
58+
if (a instanceof A) {
59+
>a instanceof A : boolean
60+
>a : A
61+
>A : typeof A
62+
63+
if (a.stringOrUndefined) {
64+
>a.stringOrUndefined : string | undefined
65+
>a : A
66+
>stringOrUndefined : string | undefined
67+
68+
new B(a.stringOrUndefined)
69+
>new B(a.stringOrUndefined) : B
70+
>B : typeof B
71+
>a.stringOrUndefined : string
72+
>a : A
73+
>stringOrUndefined : string
74+
}
75+
}
76+
77+
if (a.stringOrUndefined) {
78+
>a.stringOrUndefined : string | undefined
79+
>a : A
80+
>stringOrUndefined : string | undefined
81+
82+
if (a instanceof A) {
83+
>a instanceof A : boolean
84+
>a : A
85+
>A : typeof A
86+
87+
new B(a.stringOrUndefined)
88+
>new B(a.stringOrUndefined) : B
89+
>B : typeof B
90+
>a.stringOrUndefined : string
91+
>a : A
92+
>stringOrUndefined : string
93+
}
94+
}
95+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// @strict: true
2+
3+
// Repro from #36709
4+
5+
class A {
6+
constructor(public stringOrUndefined: string | undefined) {}
7+
}
8+
9+
class B {
10+
constructor(public str: string) {}
11+
}
12+
13+
const a = new A("123");
14+
15+
if (a instanceof A && a.stringOrUndefined) {
16+
new B(a.stringOrUndefined)
17+
}
18+
19+
if (a.stringOrUndefined && a instanceof A) {
20+
new B(a.stringOrUndefined)
21+
}
22+
23+
if (a instanceof A) {
24+
if (a.stringOrUndefined) {
25+
new B(a.stringOrUndefined)
26+
}
27+
}
28+
29+
if (a.stringOrUndefined) {
30+
if (a instanceof A) {
31+
new B(a.stringOrUndefined)
32+
}
33+
}

0 commit comments

Comments
 (0)