Skip to content

Commit 8a05707

Browse files
authored
Fix 31995: make cached key more precise to avoid returning wrong cached value. (microsoft#39670)
* fix 31995 * revert useless change only for debug. * add test
1 parent 294a040 commit 8a05707

6 files changed

+329
-4
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20097,7 +20097,7 @@ namespace ts {
2009720097
const symbol = getResolvedSymbol(<Identifier>node);
2009820098
return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined;
2009920099
case SyntaxKind.ThisKeyword:
20100-
return "0";
20100+
return `0|${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}`;
2010120101
case SyntaxKind.NonNullExpression:
2010220102
case SyntaxKind.ParenthesizedExpression:
2010320103
return getFlowCacheKey((<NonNullExpression | ParenthesizedExpression>node).expression, declaredType, initialType, flowContainer);
@@ -20960,7 +20960,7 @@ namespace ts {
2096020960

2096120961
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) {
2096220962
let key: string | undefined;
20963-
let keySet = false;
20963+
let isKeySet = false;
2096420964
let flowDepth = 0;
2096520965
if (flowAnalysisDisabled) {
2096620966
return errorType;
@@ -20983,10 +20983,10 @@ namespace ts {
2098320983
return resultType;
2098420984

2098520985
function getOrSetCacheKey() {
20986-
if (keySet) {
20986+
if (isKeySet) {
2098720987
return key;
2098820988
}
20989-
keySet = true;
20989+
isKeySet = true;
2099020990
return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer);
2099120991
}
2099220992

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
tests/cases/conformance/classes/members/instanceAndStaticMembers/typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts(29,13): error TS2322: Type 'string | number' is not assignable to type 'number'.
2+
Type 'string' is not assignable to type 'number'.
3+
4+
5+
==== tests/cases/conformance/classes/members/instanceAndStaticMembers/typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts (1 errors) ====
6+
// #31995
7+
type State = {
8+
type: "numberVariant";
9+
data: number;
10+
} | {
11+
type: "stringVariant";
12+
data: string;
13+
};
14+
15+
class SomeClass {
16+
state!: State;
17+
method() {
18+
while (0) { }
19+
this.state.data;
20+
if (this.state.type === "stringVariant") {
21+
const s: string = this.state.data;
22+
}
23+
}
24+
}
25+
26+
class SomeClass2 {
27+
state!: State;
28+
method() {
29+
const c = false;
30+
while (c) { }
31+
if (this.state.type === "numberVariant") {
32+
this.state.data;
33+
}
34+
let n: number = this.state?.data; // This should be an error
35+
~
36+
!!! error TS2322: Type 'string | number' is not assignable to type 'number'.
37+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
38+
}
39+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//// [typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts]
2+
// #31995
3+
type State = {
4+
type: "numberVariant";
5+
data: number;
6+
} | {
7+
type: "stringVariant";
8+
data: string;
9+
};
10+
11+
class SomeClass {
12+
state!: State;
13+
method() {
14+
while (0) { }
15+
this.state.data;
16+
if (this.state.type === "stringVariant") {
17+
const s: string = this.state.data;
18+
}
19+
}
20+
}
21+
22+
class SomeClass2 {
23+
state!: State;
24+
method() {
25+
const c = false;
26+
while (c) { }
27+
if (this.state.type === "numberVariant") {
28+
this.state.data;
29+
}
30+
let n: number = this.state?.data; // This should be an error
31+
}
32+
}
33+
34+
//// [typeOfThisInstanceMemberNarrowedWithLoopAntecedent.js]
35+
var SomeClass = /** @class */ (function () {
36+
function SomeClass() {
37+
}
38+
SomeClass.prototype.method = function () {
39+
while (0) { }
40+
this.state.data;
41+
if (this.state.type === "stringVariant") {
42+
var s = this.state.data;
43+
}
44+
};
45+
return SomeClass;
46+
}());
47+
var SomeClass2 = /** @class */ (function () {
48+
function SomeClass2() {
49+
}
50+
SomeClass2.prototype.method = function () {
51+
var _a;
52+
var c = false;
53+
while (c) { }
54+
if (this.state.type === "numberVariant") {
55+
this.state.data;
56+
}
57+
var n = (_a = this.state) === null || _a === void 0 ? void 0 : _a.data; // This should be an error
58+
};
59+
return SomeClass2;
60+
}());
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
=== tests/cases/conformance/classes/members/instanceAndStaticMembers/typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts ===
2+
// #31995
3+
type State = {
4+
>State : Symbol(State, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 0, 0))
5+
6+
type: "numberVariant";
7+
>type : Symbol(type, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 1, 14))
8+
9+
data: number;
10+
>data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 2, 26))
11+
12+
} | {
13+
type: "stringVariant";
14+
>type : Symbol(type, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 4, 5))
15+
16+
data: string;
17+
>data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 5, 26))
18+
19+
};
20+
21+
class SomeClass {
22+
>SomeClass : Symbol(SomeClass, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 7, 2))
23+
24+
state!: State;
25+
>state : Symbol(SomeClass.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 9, 17))
26+
>State : Symbol(State, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 0, 0))
27+
28+
method() {
29+
>method : Symbol(SomeClass.method, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 10, 18))
30+
31+
while (0) { }
32+
this.state.data;
33+
>this.state.data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 2, 26), Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 5, 26))
34+
>this.state : Symbol(SomeClass.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 9, 17))
35+
>this : Symbol(SomeClass, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 7, 2))
36+
>state : Symbol(SomeClass.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 9, 17))
37+
>data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 2, 26), Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 5, 26))
38+
39+
if (this.state.type === "stringVariant") {
40+
>this.state.type : Symbol(type, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 1, 14), Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 4, 5))
41+
>this.state : Symbol(SomeClass.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 9, 17))
42+
>this : Symbol(SomeClass, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 7, 2))
43+
>state : Symbol(SomeClass.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 9, 17))
44+
>type : Symbol(type, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 1, 14), Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 4, 5))
45+
46+
const s: string = this.state.data;
47+
>s : Symbol(s, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 15, 17))
48+
>this.state.data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 5, 26))
49+
>this.state : Symbol(SomeClass.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 9, 17))
50+
>this : Symbol(SomeClass, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 7, 2))
51+
>state : Symbol(SomeClass.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 9, 17))
52+
>data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 5, 26))
53+
}
54+
}
55+
}
56+
57+
class SomeClass2 {
58+
>SomeClass2 : Symbol(SomeClass2, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 18, 1))
59+
60+
state!: State;
61+
>state : Symbol(SomeClass2.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 20, 18))
62+
>State : Symbol(State, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 0, 0))
63+
64+
method() {
65+
>method : Symbol(SomeClass2.method, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 21, 18))
66+
67+
const c = false;
68+
>c : Symbol(c, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 23, 13))
69+
70+
while (c) { }
71+
>c : Symbol(c, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 23, 13))
72+
73+
if (this.state.type === "numberVariant") {
74+
>this.state.type : Symbol(type, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 1, 14), Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 4, 5))
75+
>this.state : Symbol(SomeClass2.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 20, 18))
76+
>this : Symbol(SomeClass2, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 18, 1))
77+
>state : Symbol(SomeClass2.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 20, 18))
78+
>type : Symbol(type, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 1, 14), Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 4, 5))
79+
80+
this.state.data;
81+
>this.state.data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 2, 26))
82+
>this.state : Symbol(SomeClass2.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 20, 18))
83+
>this : Symbol(SomeClass2, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 18, 1))
84+
>state : Symbol(SomeClass2.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 20, 18))
85+
>data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 2, 26))
86+
}
87+
let n: number = this.state?.data; // This should be an error
88+
>n : Symbol(n, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 28, 11))
89+
>this.state?.data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 2, 26), Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 5, 26))
90+
>this.state : Symbol(SomeClass2.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 20, 18))
91+
>this : Symbol(SomeClass2, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 18, 1))
92+
>state : Symbol(SomeClass2.state, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 20, 18))
93+
>data : Symbol(data, Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 2, 26), Decl(typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts, 5, 26))
94+
}
95+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
=== tests/cases/conformance/classes/members/instanceAndStaticMembers/typeOfThisInstanceMemberNarrowedWithLoopAntecedent.ts ===
2+
// #31995
3+
type State = {
4+
>State : State
5+
6+
type: "numberVariant";
7+
>type : "numberVariant"
8+
9+
data: number;
10+
>data : number
11+
12+
} | {
13+
type: "stringVariant";
14+
>type : "stringVariant"
15+
16+
data: string;
17+
>data : string
18+
19+
};
20+
21+
class SomeClass {
22+
>SomeClass : SomeClass
23+
24+
state!: State;
25+
>state : State
26+
27+
method() {
28+
>method : () => void
29+
30+
while (0) { }
31+
>0 : 0
32+
33+
this.state.data;
34+
>this.state.data : string | number
35+
>this.state : State
36+
>this : this
37+
>state : State
38+
>data : string | number
39+
40+
if (this.state.type === "stringVariant") {
41+
>this.state.type === "stringVariant" : boolean
42+
>this.state.type : "numberVariant" | "stringVariant"
43+
>this.state : State
44+
>this : this
45+
>state : State
46+
>type : "numberVariant" | "stringVariant"
47+
>"stringVariant" : "stringVariant"
48+
49+
const s: string = this.state.data;
50+
>s : string
51+
>this.state.data : string
52+
>this.state : { type: "stringVariant"; data: string; }
53+
>this : this
54+
>state : { type: "stringVariant"; data: string; }
55+
>data : string
56+
}
57+
}
58+
}
59+
60+
class SomeClass2 {
61+
>SomeClass2 : SomeClass2
62+
63+
state!: State;
64+
>state : State
65+
66+
method() {
67+
>method : () => void
68+
69+
const c = false;
70+
>c : false
71+
>false : false
72+
73+
while (c) { }
74+
>c : false
75+
76+
if (this.state.type === "numberVariant") {
77+
>this.state.type === "numberVariant" : boolean
78+
>this.state.type : "numberVariant" | "stringVariant"
79+
>this.state : State
80+
>this : this
81+
>state : State
82+
>type : "numberVariant" | "stringVariant"
83+
>"numberVariant" : "numberVariant"
84+
85+
this.state.data;
86+
>this.state.data : number
87+
>this.state : { type: "numberVariant"; data: number; }
88+
>this : this
89+
>state : { type: "numberVariant"; data: number; }
90+
>data : number
91+
}
92+
let n: number = this.state?.data; // This should be an error
93+
>n : number
94+
>this.state?.data : string | number
95+
>this.state : State
96+
>this : this
97+
>state : State
98+
>data : string | number
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// #31995
2+
type State = {
3+
type: "numberVariant";
4+
data: number;
5+
} | {
6+
type: "stringVariant";
7+
data: string;
8+
};
9+
10+
class SomeClass {
11+
state!: State;
12+
method() {
13+
while (0) { }
14+
this.state.data;
15+
if (this.state.type === "stringVariant") {
16+
const s: string = this.state.data;
17+
}
18+
}
19+
}
20+
21+
class SomeClass2 {
22+
state!: State;
23+
method() {
24+
const c = false;
25+
while (c) { }
26+
if (this.state.type === "numberVariant") {
27+
this.state.data;
28+
}
29+
let n: number = this.state?.data; // This should be an error
30+
}
31+
}

0 commit comments

Comments
 (0)