diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dd56dc8ff8905..a3808fd56e21a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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; diff --git a/tests/baselines/reference/noUncheckedIndexAccess.js b/tests/baselines/reference/noUncheckedIndexAccess.js new file mode 100644 index 0000000000000..cde4a72ff6d00 --- /dev/null +++ b/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]; diff --git a/tests/baselines/reference/noUncheckedIndexAccess.symbols b/tests/baselines/reference/noUncheckedIndexAccess.symbols new file mode 100644 index 0000000000000..b9317e08c6a72 --- /dev/null +++ b/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)) + diff --git a/tests/baselines/reference/noUncheckedIndexAccess.types b/tests/baselines/reference/noUncheckedIndexAccess.types new file mode 100644 index 0000000000000..d50f53e442da3 --- /dev/null +++ b/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 + diff --git a/tests/cases/compiler/noUncheckedIndexAccess.ts b/tests/cases/compiler/noUncheckedIndexAccess.ts new file mode 100644 index 0000000000000..826459036e593 --- /dev/null +++ b/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]; \ No newline at end of file