Skip to content

Commit bc9cbbe

Browse files
authoredSep 26, 2022
Merge pull request #49912 from microsoft/fix/47508
fix(47508): noUncheckedIndexedAccess with enums Type narrowed
2 parents 5a10f46 + 80ae43d commit bc9cbbe

File tree

5 files changed

+274
-1
lines changed

5 files changed

+274
-1
lines changed
 

‎src/compiler/checker.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -15814,7 +15814,18 @@ namespace ts {
1581415814
return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type;
1581515815
}
1581615816
errorIfWritingToReadonlyIndex(indexInfo);
15817-
return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type;
15817+
// When accessing an enum object with its own type,
15818+
// e.g. E[E.A] for enum E { A }, undefined shouldn't
15819+
// be included in the result type
15820+
if ((accessFlags & AccessFlags.IncludeUndefined) &&
15821+
!(objectType.symbol &&
15822+
objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum) &&
15823+
(indexType.symbol &&
15824+
indexType.flags & TypeFlags.EnumLiteral &&
15825+
getParentOfSymbol(indexType.symbol) === objectType.symbol))) {
15826+
return getUnionType([indexInfo.type, undefinedType]);
15827+
}
15828+
return indexInfo.type;
1581815829
}
1581915830
if (indexType.flags & TypeFlags.Never) {
1582015831
return neverType;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//// [noUncheckedIndexAccess.ts]
2+
enum Meat {
3+
Sausage,
4+
Bacon
5+
}
6+
const sausage = Meat.Sausage
7+
const valueSausage = Meat[sausage]
8+
9+
const bacon = Meat.Bacon
10+
const valueBacon = Meat[bacon]
11+
12+
const union: Meat.Bacon | Meat.Sausage = Meat.Bacon
13+
const valueUnion = Meat[union]
14+
15+
//Avoiding a false positive
16+
const value = Meat[0]
17+
18+
const valueUndefined = "testing"
19+
const value2 = Meat[valueUndefined]
20+
21+
enum A {
22+
a, b, c
23+
}
24+
enum B {
25+
x, y, z
26+
}
27+
28+
const value3 = A[B.x];
29+
30+
//// [noUncheckedIndexAccess.js]
31+
var Meat;
32+
(function (Meat) {
33+
Meat[Meat["Sausage"] = 0] = "Sausage";
34+
Meat[Meat["Bacon"] = 1] = "Bacon";
35+
})(Meat || (Meat = {}));
36+
var sausage = Meat.Sausage;
37+
var valueSausage = Meat[sausage];
38+
var bacon = Meat.Bacon;
39+
var valueBacon = Meat[bacon];
40+
var union = Meat.Bacon;
41+
var valueUnion = Meat[union];
42+
//Avoiding a false positive
43+
var value = Meat[0];
44+
var valueUndefined = "testing";
45+
var value2 = Meat[valueUndefined];
46+
var A;
47+
(function (A) {
48+
A[A["a"] = 0] = "a";
49+
A[A["b"] = 1] = "b";
50+
A[A["c"] = 2] = "c";
51+
})(A || (A = {}));
52+
var B;
53+
(function (B) {
54+
B[B["x"] = 0] = "x";
55+
B[B["y"] = 1] = "y";
56+
B[B["z"] = 2] = "z";
57+
})(B || (B = {}));
58+
var value3 = A[B.x];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
=== tests/cases/compiler/noUncheckedIndexAccess.ts ===
2+
enum Meat {
3+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
4+
5+
Sausage,
6+
>Sausage : Symbol(Meat.Sausage, Decl(noUncheckedIndexAccess.ts, 0, 11))
7+
8+
Bacon
9+
>Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
10+
}
11+
const sausage = Meat.Sausage
12+
>sausage : Symbol(sausage, Decl(noUncheckedIndexAccess.ts, 4, 7))
13+
>Meat.Sausage : Symbol(Meat.Sausage, Decl(noUncheckedIndexAccess.ts, 0, 11))
14+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
15+
>Sausage : Symbol(Meat.Sausage, Decl(noUncheckedIndexAccess.ts, 0, 11))
16+
17+
const valueSausage = Meat[sausage]
18+
>valueSausage : Symbol(valueSausage, Decl(noUncheckedIndexAccess.ts, 5, 7))
19+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
20+
>sausage : Symbol(sausage, Decl(noUncheckedIndexAccess.ts, 4, 7))
21+
22+
const bacon = Meat.Bacon
23+
>bacon : Symbol(bacon, Decl(noUncheckedIndexAccess.ts, 7, 7))
24+
>Meat.Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
25+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
26+
>Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
27+
28+
const valueBacon = Meat[bacon]
29+
>valueBacon : Symbol(valueBacon, Decl(noUncheckedIndexAccess.ts, 8, 7))
30+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
31+
>bacon : Symbol(bacon, Decl(noUncheckedIndexAccess.ts, 7, 7))
32+
33+
const union: Meat.Bacon | Meat.Sausage = Meat.Bacon
34+
>union : Symbol(union, Decl(noUncheckedIndexAccess.ts, 10, 7))
35+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
36+
>Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
37+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
38+
>Sausage : Symbol(Meat.Sausage, Decl(noUncheckedIndexAccess.ts, 0, 11))
39+
>Meat.Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
40+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
41+
>Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
42+
43+
const valueUnion = Meat[union]
44+
>valueUnion : Symbol(valueUnion, Decl(noUncheckedIndexAccess.ts, 11, 7))
45+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
46+
>union : Symbol(union, Decl(noUncheckedIndexAccess.ts, 10, 7))
47+
48+
//Avoiding a false positive
49+
const value = Meat[0]
50+
>value : Symbol(value, Decl(noUncheckedIndexAccess.ts, 14, 7))
51+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
52+
53+
const valueUndefined = "testing"
54+
>valueUndefined : Symbol(valueUndefined, Decl(noUncheckedIndexAccess.ts, 16, 7))
55+
56+
const value2 = Meat[valueUndefined]
57+
>value2 : Symbol(value2, Decl(noUncheckedIndexAccess.ts, 17, 7))
58+
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
59+
>valueUndefined : Symbol(valueUndefined, Decl(noUncheckedIndexAccess.ts, 16, 7))
60+
61+
enum A {
62+
>A : Symbol(A, Decl(noUncheckedIndexAccess.ts, 17, 37))
63+
64+
a, b, c
65+
>a : Symbol(A.a, Decl(noUncheckedIndexAccess.ts, 19, 10))
66+
>b : Symbol(A.b, Decl(noUncheckedIndexAccess.ts, 20, 6))
67+
>c : Symbol(A.c, Decl(noUncheckedIndexAccess.ts, 20, 9))
68+
}
69+
enum B {
70+
>B : Symbol(B, Decl(noUncheckedIndexAccess.ts, 21, 3))
71+
72+
x, y, z
73+
>x : Symbol(B.x, Decl(noUncheckedIndexAccess.ts, 22, 10))
74+
>y : Symbol(B.y, Decl(noUncheckedIndexAccess.ts, 23, 6))
75+
>z : Symbol(B.z, Decl(noUncheckedIndexAccess.ts, 23, 9))
76+
}
77+
78+
const value3 = A[B.x];
79+
>value3 : Symbol(value3, Decl(noUncheckedIndexAccess.ts, 26, 7))
80+
>A : Symbol(A, Decl(noUncheckedIndexAccess.ts, 17, 37))
81+
>B.x : Symbol(B.x, Decl(noUncheckedIndexAccess.ts, 22, 10))
82+
>B : Symbol(B, Decl(noUncheckedIndexAccess.ts, 21, 3))
83+
>x : Symbol(B.x, Decl(noUncheckedIndexAccess.ts, 22, 10))
84+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
=== tests/cases/compiler/noUncheckedIndexAccess.ts ===
2+
enum Meat {
3+
>Meat : Meat
4+
5+
Sausage,
6+
>Sausage : Meat.Sausage
7+
8+
Bacon
9+
>Bacon : Meat.Bacon
10+
}
11+
const sausage = Meat.Sausage
12+
>sausage : Meat.Sausage
13+
>Meat.Sausage : Meat.Sausage
14+
>Meat : typeof Meat
15+
>Sausage : Meat.Sausage
16+
17+
const valueSausage = Meat[sausage]
18+
>valueSausage : string
19+
>Meat[sausage] : string
20+
>Meat : typeof Meat
21+
>sausage : Meat.Sausage
22+
23+
const bacon = Meat.Bacon
24+
>bacon : Meat.Bacon
25+
>Meat.Bacon : Meat.Bacon
26+
>Meat : typeof Meat
27+
>Bacon : Meat.Bacon
28+
29+
const valueBacon = Meat[bacon]
30+
>valueBacon : string
31+
>Meat[bacon] : string
32+
>Meat : typeof Meat
33+
>bacon : Meat.Bacon
34+
35+
const union: Meat.Bacon | Meat.Sausage = Meat.Bacon
36+
>union : Meat
37+
>Meat : any
38+
>Meat : any
39+
>Meat.Bacon : Meat.Bacon
40+
>Meat : typeof Meat
41+
>Bacon : Meat.Bacon
42+
43+
const valueUnion = Meat[union]
44+
>valueUnion : string
45+
>Meat[union] : string
46+
>Meat : typeof Meat
47+
>union : Meat.Bacon
48+
49+
//Avoiding a false positive
50+
const value = Meat[0]
51+
>value : string | undefined
52+
>Meat[0] : string | undefined
53+
>Meat : typeof Meat
54+
>0 : 0
55+
56+
const valueUndefined = "testing"
57+
>valueUndefined : "testing"
58+
>"testing" : "testing"
59+
60+
const value2 = Meat[valueUndefined]
61+
>value2 : error
62+
>Meat[valueUndefined] : error
63+
>Meat : typeof Meat
64+
>valueUndefined : "testing"
65+
66+
enum A {
67+
>A : A
68+
69+
a, b, c
70+
>a : A.a
71+
>b : A.b
72+
>c : A.c
73+
}
74+
enum B {
75+
>B : B
76+
77+
x, y, z
78+
>x : B.x
79+
>y : B.y
80+
>z : B.z
81+
}
82+
83+
const value3 = A[B.x];
84+
>value3 : string | undefined
85+
>A[B.x] : string | undefined
86+
>A : typeof A
87+
>B.x : B.x
88+
>B : typeof B
89+
>x : B.x
90+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//@noUncheckedIndexedAccess: true
2+
//@strictNullChecks: true
3+
4+
enum Meat {
5+
Sausage,
6+
Bacon
7+
}
8+
const sausage = Meat.Sausage
9+
const valueSausage = Meat[sausage]
10+
11+
const bacon = Meat.Bacon
12+
const valueBacon = Meat[bacon]
13+
14+
const union: Meat.Bacon | Meat.Sausage = Meat.Bacon
15+
const valueUnion = Meat[union]
16+
17+
//Avoiding a false positive
18+
const value = Meat[0]
19+
20+
const valueUndefined = "testing"
21+
const value2 = Meat[valueUndefined]
22+
23+
enum A {
24+
a, b, c
25+
}
26+
enum B {
27+
x, y, z
28+
}
29+
30+
const value3 = A[B.x];

0 commit comments

Comments
 (0)
Please sign in to comment.