From d0547f1d0b06e3fff1c4eff75830462b3749aaee Mon Sep 17 00:00:00 2001 From: navya9singh Date: Thu, 14 Jul 2022 14:43:46 -0700 Subject: [PATCH 1/5] noUncheckedIndexedAccess with enums Type narrowed --- src/compiler/checker.ts | 5 ++ .../reference/noUncheckedIndexAccess.js | 37 +++++++++++ .../reference/noUncheckedIndexAccess.symbols | 60 +++++++++++++++++ .../reference/noUncheckedIndexAccess.types | 65 +++++++++++++++++++ .../cases/compiler/noUncheckedIndexAccess.ts | 21 ++++++ 5 files changed, 188 insertions(+) create mode 100644 tests/baselines/reference/noUncheckedIndexAccess.js create mode 100644 tests/baselines/reference/noUncheckedIndexAccess.symbols create mode 100644 tests/baselines/reference/noUncheckedIndexAccess.types create mode 100644 tests/cases/compiler/noUncheckedIndexAccess.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index de3c34e0679a3..c89299ce6b1b8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15589,6 +15589,11 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } errorIfWritingToReadonlyIndex(indexInfo); + if (accessFlags & AccessFlags.IncludeUndefined && objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum)) { + if(indexType.flags & TypeFlags.EnumLiteral){ + return indexInfo.type; + } + } return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } if (indexType.flags & TypeFlags.Never) { diff --git a/tests/baselines/reference/noUncheckedIndexAccess.js b/tests/baselines/reference/noUncheckedIndexAccess.js new file mode 100644 index 0000000000000..3b1e3839e1ce8 --- /dev/null +++ b/tests/baselines/reference/noUncheckedIndexAccess.js @@ -0,0 +1,37 @@ +//// [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 t = "testing" + const value2 = Meat[t] + + +//// [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 t = "testing"; +var value2 = Meat[t]; diff --git a/tests/baselines/reference/noUncheckedIndexAccess.symbols b/tests/baselines/reference/noUncheckedIndexAccess.symbols new file mode 100644 index 0000000000000..2f6c8a570610d --- /dev/null +++ b/tests/baselines/reference/noUncheckedIndexAccess.symbols @@ -0,0 +1,60 @@ +=== 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 t = "testing" +>t : Symbol(t, Decl(noUncheckedIndexAccess.ts, 16, 7)) + + const value2 = Meat[t] +>value2 : Symbol(value2, Decl(noUncheckedIndexAccess.ts, 17, 7)) +>Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0)) +>t : Symbol(t, Decl(noUncheckedIndexAccess.ts, 16, 7)) + diff --git a/tests/baselines/reference/noUncheckedIndexAccess.types b/tests/baselines/reference/noUncheckedIndexAccess.types new file mode 100644 index 0000000000000..3eab3e64bed3d --- /dev/null +++ b/tests/baselines/reference/noUncheckedIndexAccess.types @@ -0,0 +1,65 @@ +=== 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 t = "testing" +>t : "testing" +>"testing" : "testing" + + const value2 = Meat[t] +>value2 : error +>Meat[t] : error +>Meat : typeof Meat +>t : "testing" + diff --git a/tests/cases/compiler/noUncheckedIndexAccess.ts b/tests/cases/compiler/noUncheckedIndexAccess.ts new file mode 100644 index 0000000000000..0891d9e40ae4b --- /dev/null +++ b/tests/cases/compiler/noUncheckedIndexAccess.ts @@ -0,0 +1,21 @@ +//@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 t = "testing" + const value2 = Meat[t] From 9b1cbe059bbe97ffcb78861ad463f1ff84c48026 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Tue, 19 Jul 2022 14:38:24 -0700 Subject: [PATCH 2/5] Added type checks to avoid false positive results --- src/compiler/checker.ts | 4 +-- .../reference/noUncheckedIndexAccess.js | 31 +++++++++++++--- .../reference/noUncheckedIndexAccess.symbols | 32 ++++++++++++++--- .../reference/noUncheckedIndexAccess.types | 35 ++++++++++++++++--- .../cases/compiler/noUncheckedIndexAccess.ts | 13 +++++-- 5 files changed, 97 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c89299ce6b1b8..679e79ded0c1a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15589,8 +15589,8 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } errorIfWritingToReadonlyIndex(indexInfo); - if (accessFlags & AccessFlags.IncludeUndefined && objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum)) { - if(indexType.flags & TypeFlags.EnumLiteral){ + if (accessFlags & AccessFlags.IncludeUndefined && objectType.symbol && objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum)) { + if (indexType.symbol && indexType.flags & TypeFlags.EnumLiteral && getParentOfSymbol(indexType.symbol) === objectType.symbol) { return indexInfo.type; } } diff --git a/tests/baselines/reference/noUncheckedIndexAccess.js b/tests/baselines/reference/noUncheckedIndexAccess.js index 3b1e3839e1ce8..cde4a72ff6d00 100644 --- a/tests/baselines/reference/noUncheckedIndexAccess.js +++ b/tests/baselines/reference/noUncheckedIndexAccess.js @@ -15,9 +15,17 @@ enum Meat { //Avoiding a false positive const value = Meat[0] - const t = "testing" - const value2 = Meat[t] - + 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; @@ -33,5 +41,18 @@ var union = Meat.Bacon; var valueUnion = Meat[union]; //Avoiding a false positive var value = Meat[0]; -var t = "testing"; -var value2 = Meat[t]; +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 index 2f6c8a570610d..b9317e08c6a72 100644 --- a/tests/baselines/reference/noUncheckedIndexAccess.symbols +++ b/tests/baselines/reference/noUncheckedIndexAccess.symbols @@ -50,11 +50,35 @@ enum Meat { >value : Symbol(value, Decl(noUncheckedIndexAccess.ts, 14, 7)) >Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0)) - const t = "testing" ->t : Symbol(t, Decl(noUncheckedIndexAccess.ts, 16, 7)) + const valueUndefined = "testing" +>valueUndefined : Symbol(valueUndefined, Decl(noUncheckedIndexAccess.ts, 16, 7)) - const value2 = Meat[t] + const value2 = Meat[valueUndefined] >value2 : Symbol(value2, Decl(noUncheckedIndexAccess.ts, 17, 7)) >Meat : Symbol(Meat, Decl(noUncheckedIndexAccess.ts, 0, 0)) ->t : Symbol(t, Decl(noUncheckedIndexAccess.ts, 16, 7)) +>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 index 3eab3e64bed3d..d50f53e442da3 100644 --- a/tests/baselines/reference/noUncheckedIndexAccess.types +++ b/tests/baselines/reference/noUncheckedIndexAccess.types @@ -53,13 +53,38 @@ enum Meat { >Meat : typeof Meat >0 : 0 - const t = "testing" ->t : "testing" + const valueUndefined = "testing" +>valueUndefined : "testing" >"testing" : "testing" - const value2 = Meat[t] + const value2 = Meat[valueUndefined] >value2 : error ->Meat[t] : error +>Meat[valueUndefined] : error >Meat : typeof Meat ->t : "testing" +>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 index 0891d9e40ae4b..826459036e593 100644 --- a/tests/cases/compiler/noUncheckedIndexAccess.ts +++ b/tests/cases/compiler/noUncheckedIndexAccess.ts @@ -17,5 +17,14 @@ enum Meat { //Avoiding a false positive const value = Meat[0] - const t = "testing" - const value2 = Meat[t] + 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 From cd312d3076c054cdf9ed6f33c2d3e3995bca92eb Mon Sep 17 00:00:00 2001 From: navya9singh Date: Wed, 31 Aug 2022 13:13:29 -0700 Subject: [PATCH 3/5] Managing control flow --- src/compiler/checker.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 679e79ded0c1a..f5e29d66dc181 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15589,12 +15589,13 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } errorIfWritingToReadonlyIndex(indexInfo); - if (accessFlags & AccessFlags.IncludeUndefined && objectType.symbol && objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum)) { - if (indexType.symbol && indexType.flags & TypeFlags.EnumLiteral && getParentOfSymbol(indexType.symbol) === objectType.symbol) { + if (accessFlags & AccessFlags.IncludeUndefined) { + if (objectType.symbol && objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum) && (indexType.symbol && indexType.flags & TypeFlags.EnumLiteral && getParentOfSymbol(indexType.symbol) === objectType.symbol)) { return indexInfo.type; } + return getUnionType([indexInfo.type, undefinedType]); } - return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + return indexInfo.type; } if (indexType.flags & TypeFlags.Never) { return neverType; From ba10a0d7c06df259e620645a8d6fd9a5262d8b5d Mon Sep 17 00:00:00 2001 From: navya9singh Date: Thu, 15 Sep 2022 15:56:31 -0700 Subject: [PATCH 4/5] Removing duplicated code --- src/compiler/checker.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f5e29d66dc181..3e823c25e641b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15589,10 +15589,15 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } errorIfWritingToReadonlyIndex(indexInfo); - if (accessFlags & AccessFlags.IncludeUndefined) { - if (objectType.symbol && objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum) && (indexType.symbol && indexType.flags & TypeFlags.EnumLiteral && getParentOfSymbol(indexType.symbol) === objectType.symbol)) { - return 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; From 80ae43d2399503a04651e3705823137d36148b00 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Tue, 20 Sep 2022 12:13:21 -0700 Subject: [PATCH 5/5] Fixing spaces --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3e823c25e641b..09aaf4557149e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15589,7 +15589,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } errorIfWritingToReadonlyIndex(indexInfo); - // When accessing an enum object with its own 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) &&