Skip to content

Commit

Permalink
Merge pull request #49912 from microsoft/fix/47508
Browse files Browse the repository at this point in the history
fix(47508): noUncheckedIndexedAccess with enums Type narrowed
  • Loading branch information
navya9singh committed Sep 26, 2022
2 parents 5a10f46 + 80ae43d commit bc9cbbe
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 1 deletion.
13 changes: 12 additions & 1 deletion src/compiler/checker.ts
Expand Up @@ -15814,7 +15814,18 @@ namespace ts {
return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type;
}
errorIfWritingToReadonlyIndex(indexInfo);
return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type;
// When accessing an enum object with its own type,
// e.g. E[E.A] for enum E { A }, undefined shouldn't
// be included in the result type
if ((accessFlags & AccessFlags.IncludeUndefined) &&
!(objectType.symbol &&
objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum) &&
(indexType.symbol &&
indexType.flags & TypeFlags.EnumLiteral &&
getParentOfSymbol(indexType.symbol) === objectType.symbol))) {
return getUnionType([indexInfo.type, undefinedType]);
}
return indexInfo.type;
}
if (indexType.flags & TypeFlags.Never) {
return neverType;
Expand Down
58 changes: 58 additions & 0 deletions tests/baselines/reference/noUncheckedIndexAccess.js
@@ -0,0 +1,58 @@
//// [noUncheckedIndexAccess.ts]
enum Meat {
Sausage,
Bacon
}
const sausage = Meat.Sausage
const valueSausage = Meat[sausage]

const bacon = Meat.Bacon
const valueBacon = Meat[bacon]

const union: Meat.Bacon | Meat.Sausage = Meat.Bacon
const valueUnion = Meat[union]

//Avoiding a false positive
const value = Meat[0]

const valueUndefined = "testing"
const value2 = Meat[valueUndefined]

enum A {
a, b, c
}
enum B {
x, y, z
}

const value3 = A[B.x];

//// [noUncheckedIndexAccess.js]
var Meat;
(function (Meat) {
Meat[Meat["Sausage"] = 0] = "Sausage";
Meat[Meat["Bacon"] = 1] = "Bacon";
})(Meat || (Meat = {}));
var sausage = Meat.Sausage;
var valueSausage = Meat[sausage];
var bacon = Meat.Bacon;
var valueBacon = Meat[bacon];
var union = Meat.Bacon;
var valueUnion = Meat[union];
//Avoiding a false positive
var value = Meat[0];
var valueUndefined = "testing";
var value2 = Meat[valueUndefined];
var A;
(function (A) {
A[A["a"] = 0] = "a";
A[A["b"] = 1] = "b";
A[A["c"] = 2] = "c";
})(A || (A = {}));
var B;
(function (B) {
B[B["x"] = 0] = "x";
B[B["y"] = 1] = "y";
B[B["z"] = 2] = "z";
})(B || (B = {}));
var value3 = A[B.x];
84 changes: 84 additions & 0 deletions tests/baselines/reference/noUncheckedIndexAccess.symbols
@@ -0,0 +1,84 @@
=== tests/cases/compiler/noUncheckedIndexAccess.ts ===
enum Meat {
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))

Sausage,
>Sausage : Symbol(Meat.Sausage, Decl(noUncheckedIndexAccess.ts, 0, 11))

Bacon
>Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
}
const sausage = Meat.Sausage
>sausage : Symbol(sausage, Decl(noUncheckedIndexAccess.ts, 4, 7))
>Meat.Sausage : Symbol(Meat.Sausage, Decl(noUncheckedIndexAccess.ts, 0, 11))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
>Sausage : Symbol(Meat.Sausage, Decl(noUncheckedIndexAccess.ts, 0, 11))

const valueSausage = Meat[sausage]
>valueSausage : Symbol(valueSausage, Decl(noUncheckedIndexAccess.ts, 5, 7))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
>sausage : Symbol(sausage, Decl(noUncheckedIndexAccess.ts, 4, 7))

const bacon = Meat.Bacon
>bacon : Symbol(bacon, Decl(noUncheckedIndexAccess.ts, 7, 7))
>Meat.Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
>Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))

const valueBacon = Meat[bacon]
>valueBacon : Symbol(valueBacon, Decl(noUncheckedIndexAccess.ts, 8, 7))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
>bacon : Symbol(bacon, Decl(noUncheckedIndexAccess.ts, 7, 7))

const union: Meat.Bacon | Meat.Sausage = Meat.Bacon
>union : Symbol(union, Decl(noUncheckedIndexAccess.ts, 10, 7))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
>Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
>Sausage : Symbol(Meat.Sausage, Decl(noUncheckedIndexAccess.ts, 0, 11))
>Meat.Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
>Bacon : Symbol(Meat.Bacon, Decl(noUncheckedIndexAccess.ts, 1, 12))

const valueUnion = Meat[union]
>valueUnion : Symbol(valueUnion, Decl(noUncheckedIndexAccess.ts, 11, 7))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
>union : Symbol(union, Decl(noUncheckedIndexAccess.ts, 10, 7))

//Avoiding a false positive
const value = Meat[0]
>value : Symbol(value, Decl(noUncheckedIndexAccess.ts, 14, 7))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))

const valueUndefined = "testing"
>valueUndefined : Symbol(valueUndefined, Decl(noUncheckedIndexAccess.ts, 16, 7))

const value2 = Meat[valueUndefined]
>value2 : Symbol(value2, Decl(noUncheckedIndexAccess.ts, 17, 7))
>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0))
>valueUndefined : Symbol(valueUndefined, Decl(noUncheckedIndexAccess.ts, 16, 7))

enum A {
>A : Symbol(A, Decl(noUncheckedIndexAccess.ts, 17, 37))

a, b, c
>a : Symbol(A.a, Decl(noUncheckedIndexAccess.ts, 19, 10))
>b : Symbol(A.b, Decl(noUncheckedIndexAccess.ts, 20, 6))
>c : Symbol(A.c, Decl(noUncheckedIndexAccess.ts, 20, 9))
}
enum B {
>B : Symbol(B, Decl(noUncheckedIndexAccess.ts, 21, 3))

x, y, z
>x : Symbol(B.x, Decl(noUncheckedIndexAccess.ts, 22, 10))
>y : Symbol(B.y, Decl(noUncheckedIndexAccess.ts, 23, 6))
>z : Symbol(B.z, Decl(noUncheckedIndexAccess.ts, 23, 9))
}

const value3 = A[B.x];
>value3 : Symbol(value3, Decl(noUncheckedIndexAccess.ts, 26, 7))
>A : Symbol(A, Decl(noUncheckedIndexAccess.ts, 17, 37))
>B.x : Symbol(B.x, Decl(noUncheckedIndexAccess.ts, 22, 10))
>B : Symbol(B, Decl(noUncheckedIndexAccess.ts, 21, 3))
>x : Symbol(B.x, Decl(noUncheckedIndexAccess.ts, 22, 10))

90 changes: 90 additions & 0 deletions tests/baselines/reference/noUncheckedIndexAccess.types
@@ -0,0 +1,90 @@
=== tests/cases/compiler/noUncheckedIndexAccess.ts ===
enum Meat {
>Meat : Meat

Sausage,
>Sausage : Meat.Sausage

Bacon
>Bacon : Meat.Bacon
}
const sausage = Meat.Sausage
>sausage : Meat.Sausage
>Meat.Sausage : Meat.Sausage
>Meat : typeof Meat
>Sausage : Meat.Sausage

const valueSausage = Meat[sausage]
>valueSausage : string
>Meat[sausage] : string
>Meat : typeof Meat
>sausage : Meat.Sausage

const bacon = Meat.Bacon
>bacon : Meat.Bacon
>Meat.Bacon : Meat.Bacon
>Meat : typeof Meat
>Bacon : Meat.Bacon

const valueBacon = Meat[bacon]
>valueBacon : string
>Meat[bacon] : string
>Meat : typeof Meat
>bacon : Meat.Bacon

const union: Meat.Bacon | Meat.Sausage = Meat.Bacon
>union : Meat
>Meat : any
>Meat : any
>Meat.Bacon : Meat.Bacon
>Meat : typeof Meat
>Bacon : Meat.Bacon

const valueUnion = Meat[union]
>valueUnion : string
>Meat[union] : string
>Meat : typeof Meat
>union : Meat.Bacon

//Avoiding a false positive
const value = Meat[0]
>value : string | undefined
>Meat[0] : string | undefined
>Meat : typeof Meat
>0 : 0

const valueUndefined = "testing"
>valueUndefined : "testing"
>"testing" : "testing"

const value2 = Meat[valueUndefined]
>value2 : error
>Meat[valueUndefined] : error
>Meat : typeof Meat
>valueUndefined : "testing"

enum A {
>A : A

a, b, c
>a : A.a
>b : A.b
>c : A.c
}
enum B {
>B : B

x, y, z
>x : B.x
>y : B.y
>z : B.z
}

const value3 = A[B.x];
>value3 : string | undefined
>A[B.x] : string | undefined
>A : typeof A
>B.x : B.x
>B : typeof B
>x : B.x

30 changes: 30 additions & 0 deletions tests/cases/compiler/noUncheckedIndexAccess.ts
@@ -0,0 +1,30 @@
//@noUncheckedIndexedAccess: true
//@strictNullChecks: true

enum Meat {
Sausage,
Bacon
}
const sausage = Meat.Sausage
const valueSausage = Meat[sausage]

const bacon = Meat.Bacon
const valueBacon = Meat[bacon]

const union: Meat.Bacon | Meat.Sausage = Meat.Bacon
const valueUnion = Meat[union]

//Avoiding a false positive
const value = Meat[0]

const valueUndefined = "testing"
const value2 = Meat[valueUndefined]

enum A {
a, b, c
}
enum B {
x, y, z
}

const value3 = A[B.x];

0 comments on commit bc9cbbe

Please sign in to comment.