Skip to content

Commit 3d235b4

Browse files
--noUncheckedIndexedAccess (microsoft#39560)
* Initial implementation + tests * linty * Support destructuring declarations and assignments * lint * Fix destructuring assignment and element access into known properties * Update baselines * Rename flag to unUncheckedIndexedAccess * Add test for unique symbol indexing * Fix flag order in baselines Co-authored-by: Andrew Branch <[email protected]>
1 parent 9c8d11b commit 3d235b4

File tree

32 files changed

+1686
-24
lines changed

32 files changed

+1686
-24
lines changed

src/compiler/checker.ts

Lines changed: 51 additions & 24 deletions
Large diffs are not rendered by default.

src/compiler/commandLineParser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,14 @@ namespace ts {
624624
category: Diagnostics.Additional_Checks,
625625
description: Diagnostics.Report_errors_for_fallthrough_cases_in_switch_statement
626626
},
627+
{
628+
name: "noUncheckedIndexedAccess",
629+
type: "boolean",
630+
affectsSemanticDiagnostics: true,
631+
showInSimplifiedHelpView: false,
632+
category: Diagnostics.Additional_Checks,
633+
description: Diagnostics.Include_undefined_in_index_signature_results
634+
},
627635

628636
// Module Resolution
629637
{

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4721,6 +4721,11 @@
47214721
"code": 6504
47224722
},
47234723

4724+
"Include 'undefined' in index signature results": {
4725+
"category": "Message",
4726+
"code": 6800
4727+
},
4728+
47244729
"Variable '{0}' implicitly has an '{1}' type.": {
47254730
"category": "Error",
47264731
"code": 7005

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5783,6 +5783,7 @@ namespace ts {
57835783
assumeChangesOnlyAffectDirectDependencies?: boolean;
57845784
noLib?: boolean;
57855785
noResolve?: boolean;
5786+
noUncheckedIndexSignatures?: boolean;
57865787
out?: string;
57875788
outDir?: string;
57885789
outFile?: string;

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2841,6 +2841,7 @@ declare namespace ts {
28412841
assumeChangesOnlyAffectDirectDependencies?: boolean;
28422842
noLib?: boolean;
28432843
noResolve?: boolean;
2844+
noUncheckedIndexSignatures?: boolean;
28442845
out?: string;
28452846
outDir?: string;
28462847
outFile?: string;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2841,6 +2841,7 @@ declare namespace ts {
28412841
assumeChangesOnlyAffectDirectDependencies?: boolean;
28422842
noLib?: boolean;
28432843
noResolve?: boolean;
2844+
noUncheckedIndexSignatures?: boolean;
28442845
out?: string;
28452846
outDir?: string;
28462847
outFile?: string;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(3,32): error TS2344: Type 'boolean | undefined' does not satisfy the constraint 'boolean'.
2+
Type 'undefined' is not assignable to type 'boolean'.
3+
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(38,1): error TS2322: Type 'undefined' is not assignable to type 'boolean'.
4+
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(39,1): error TS2322: Type 'undefined' is not assignable to type 'boolean'.
5+
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(40,1): error TS2322: Type 'undefined' is not assignable to type 'boolean'.
6+
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(41,1): error TS2322: Type 'undefined' is not assignable to type 'boolean'.
7+
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(85,1): error TS2322: Type 'undefined' is not assignable to type 'string'.
8+
9+
10+
==== tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts (6 errors) ====
11+
type CheckBooleanOnly<T extends boolean> = any;
12+
// Validate CheckBooleanOnly works - should error
13+
type T_ERR1 = CheckBooleanOnly<boolean | undefined>;
14+
~~~~~~~~~~~~~~~~~~~
15+
!!! error TS2344: Type 'boolean | undefined' does not satisfy the constraint 'boolean'.
16+
!!! error TS2344: Type 'undefined' is not assignable to type 'boolean'.
17+
18+
enum NumericEnum1 { A, B, C }
19+
enum NumericEnum2 { A = 0, B = 1 , C = 2 }
20+
enum StringEnum1 { A = "Alpha", B = "Beta" }
21+
22+
declare const strMap: { [s: string]: boolean };
23+
24+
// All of these should be errors
25+
const e1: boolean = strMap["foo"];
26+
const e2: boolean = strMap.bar;
27+
const e3: boolean = strMap[0];
28+
const e4: boolean = strMap[0 as string | number];
29+
const e5: boolean = strMap[0 as string | 0 | 1];
30+
const e6: boolean = strMap[0 as 0 | 1];
31+
const e7: boolean = strMap["foo" as "foo" | "baz"];
32+
const e8: boolean = strMap[NumericEnum1.A];
33+
const e9: boolean = strMap[NumericEnum2.A];
34+
const e10: boolean = strMap[StringEnum1.A];
35+
const e11: boolean = strMap[StringEnum1.A as StringEnum1];
36+
const e12: boolean = strMap[NumericEnum1.A as NumericEnum1];
37+
const e13: boolean = strMap[NumericEnum2.A as NumericEnum2];
38+
const e14: boolean = strMap[null as any];
39+
40+
// Should be OK
41+
const ok1: boolean | undefined = strMap["foo"];
42+
const ok2: boolean | undefined = strMap.bar;
43+
44+
type T_OK1 = CheckBooleanOnly<(typeof strMap)[string]>;
45+
type T_OK2 = CheckBooleanOnly<(typeof strMap)["foo"]>;
46+
type T_OK3 = CheckBooleanOnly<(typeof strMap)["bar" | "baz"]>;
47+
type T_OK4 = CheckBooleanOnly<(typeof strMap)[number]>;
48+
type T_OK5 = CheckBooleanOnly<(typeof strMap)[any]>;
49+
50+
// Writes don't allow 'undefined'; all should be errors
51+
strMap["baz"] = undefined;
52+
~~~~~~~~~~~~~
53+
!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'.
54+
strMap.qua = undefined;
55+
~~~~~~~~~~
56+
!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'.
57+
strMap[0] = undefined;
58+
~~~~~~~~~
59+
!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'.
60+
strMap[null as any] = undefined;
61+
~~~~~~~~~~~~~~~~~~~
62+
!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'.
63+
64+
// Numeric lookups are unaffected
65+
declare const numMap: { [s: number]: boolean };
66+
// All of these should be ok
67+
const num_ok1: boolean = numMap[0];
68+
const num_ok2: boolean = numMap[0 as number];
69+
const num_ok3: boolean = numMap[0 as 0 | 1];
70+
const num_ok4: boolean = numMap[NumericEnum1.A];
71+
const num_ok5: boolean = numMap[NumericEnum2.A];
72+
73+
// Generics
74+
function generic1<T extends { [s: string]: boolean }>(arg: T): boolean {
75+
// Should error
76+
return arg["blah"];
77+
}
78+
function generic2<T extends { [s: string]: boolean }>(arg: T): boolean {
79+
// Should OK
80+
return arg["blah"]!;
81+
}
82+
function generic3<T extends string>(arg: T): boolean {
83+
// Should error
84+
return strMap[arg];
85+
}
86+
87+
// Element access into known properties is ok
88+
declare const obj1: { x: string, y: number, [key: string]: string | number };
89+
obj1["x"];
90+
const y = "y";
91+
obj1[y];
92+
let yy = "y";
93+
obj1[yy];
94+
let z = "z";
95+
obj1[z];
96+
97+
// Distributivity cases
98+
declare const strMapUnion: { [s: string]: boolean } | { [s: string]: number };
99+
// Should error
100+
const f1: boolean | number = strMapUnion["foo"];
101+
102+
// Symbol index signatures
103+
declare const s: unique symbol;
104+
declare const symbolMap: { [s]: string };
105+
const e15: string = symbolMap[s]; // Should OK
106+
symbolMap[s] = undefined; // Should error
107+
~~~~~~~~~~~~
108+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
109+
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//// [noUncheckedIndexedAccess.ts]
2+
type CheckBooleanOnly<T extends boolean> = any;
3+
// Validate CheckBooleanOnly works - should error
4+
type T_ERR1 = CheckBooleanOnly<boolean | undefined>;
5+
6+
enum NumericEnum1 { A, B, C }
7+
enum NumericEnum2 { A = 0, B = 1 , C = 2 }
8+
enum StringEnum1 { A = "Alpha", B = "Beta" }
9+
10+
declare const strMap: { [s: string]: boolean };
11+
12+
// All of these should be errors
13+
const e1: boolean = strMap["foo"];
14+
const e2: boolean = strMap.bar;
15+
const e3: boolean = strMap[0];
16+
const e4: boolean = strMap[0 as string | number];
17+
const e5: boolean = strMap[0 as string | 0 | 1];
18+
const e6: boolean = strMap[0 as 0 | 1];
19+
const e7: boolean = strMap["foo" as "foo" | "baz"];
20+
const e8: boolean = strMap[NumericEnum1.A];
21+
const e9: boolean = strMap[NumericEnum2.A];
22+
const e10: boolean = strMap[StringEnum1.A];
23+
const e11: boolean = strMap[StringEnum1.A as StringEnum1];
24+
const e12: boolean = strMap[NumericEnum1.A as NumericEnum1];
25+
const e13: boolean = strMap[NumericEnum2.A as NumericEnum2];
26+
const e14: boolean = strMap[null as any];
27+
28+
// Should be OK
29+
const ok1: boolean | undefined = strMap["foo"];
30+
const ok2: boolean | undefined = strMap.bar;
31+
32+
type T_OK1 = CheckBooleanOnly<(typeof strMap)[string]>;
33+
type T_OK2 = CheckBooleanOnly<(typeof strMap)["foo"]>;
34+
type T_OK3 = CheckBooleanOnly<(typeof strMap)["bar" | "baz"]>;
35+
type T_OK4 = CheckBooleanOnly<(typeof strMap)[number]>;
36+
type T_OK5 = CheckBooleanOnly<(typeof strMap)[any]>;
37+
38+
// Writes don't allow 'undefined'; all should be errors
39+
strMap["baz"] = undefined;
40+
strMap.qua = undefined;
41+
strMap[0] = undefined;
42+
strMap[null as any] = undefined;
43+
44+
// Numeric lookups are unaffected
45+
declare const numMap: { [s: number]: boolean };
46+
// All of these should be ok
47+
const num_ok1: boolean = numMap[0];
48+
const num_ok2: boolean = numMap[0 as number];
49+
const num_ok3: boolean = numMap[0 as 0 | 1];
50+
const num_ok4: boolean = numMap[NumericEnum1.A];
51+
const num_ok5: boolean = numMap[NumericEnum2.A];
52+
53+
// Generics
54+
function generic1<T extends { [s: string]: boolean }>(arg: T): boolean {
55+
// Should error
56+
return arg["blah"];
57+
}
58+
function generic2<T extends { [s: string]: boolean }>(arg: T): boolean {
59+
// Should OK
60+
return arg["blah"]!;
61+
}
62+
function generic3<T extends string>(arg: T): boolean {
63+
// Should error
64+
return strMap[arg];
65+
}
66+
67+
// Element access into known properties is ok
68+
declare const obj1: { x: string, y: number, [key: string]: string | number };
69+
obj1["x"];
70+
const y = "y";
71+
obj1[y];
72+
let yy = "y";
73+
obj1[yy];
74+
let z = "z";
75+
obj1[z];
76+
77+
// Distributivity cases
78+
declare const strMapUnion: { [s: string]: boolean } | { [s: string]: number };
79+
// Should error
80+
const f1: boolean | number = strMapUnion["foo"];
81+
82+
// Symbol index signatures
83+
declare const s: unique symbol;
84+
declare const symbolMap: { [s]: string };
85+
const e15: string = symbolMap[s]; // Should OK
86+
symbolMap[s] = undefined; // Should error
87+
88+
89+
//// [noUncheckedIndexedAccess.js]
90+
"use strict";
91+
var NumericEnum1;
92+
(function (NumericEnum1) {
93+
NumericEnum1[NumericEnum1["A"] = 0] = "A";
94+
NumericEnum1[NumericEnum1["B"] = 1] = "B";
95+
NumericEnum1[NumericEnum1["C"] = 2] = "C";
96+
})(NumericEnum1 || (NumericEnum1 = {}));
97+
var NumericEnum2;
98+
(function (NumericEnum2) {
99+
NumericEnum2[NumericEnum2["A"] = 0] = "A";
100+
NumericEnum2[NumericEnum2["B"] = 1] = "B";
101+
NumericEnum2[NumericEnum2["C"] = 2] = "C";
102+
})(NumericEnum2 || (NumericEnum2 = {}));
103+
var StringEnum1;
104+
(function (StringEnum1) {
105+
StringEnum1["A"] = "Alpha";
106+
StringEnum1["B"] = "Beta";
107+
})(StringEnum1 || (StringEnum1 = {}));
108+
// All of these should be errors
109+
var e1 = strMap["foo"];
110+
var e2 = strMap.bar;
111+
var e3 = strMap[0];
112+
var e4 = strMap[0];
113+
var e5 = strMap[0];
114+
var e6 = strMap[0];
115+
var e7 = strMap["foo"];
116+
var e8 = strMap[NumericEnum1.A];
117+
var e9 = strMap[NumericEnum2.A];
118+
var e10 = strMap[StringEnum1.A];
119+
var e11 = strMap[StringEnum1.A];
120+
var e12 = strMap[NumericEnum1.A];
121+
var e13 = strMap[NumericEnum2.A];
122+
var e14 = strMap[null];
123+
// Should be OK
124+
var ok1 = strMap["foo"];
125+
var ok2 = strMap.bar;
126+
// Writes don't allow 'undefined'; all should be errors
127+
strMap["baz"] = undefined;
128+
strMap.qua = undefined;
129+
strMap[0] = undefined;
130+
strMap[null] = undefined;
131+
// All of these should be ok
132+
var num_ok1 = numMap[0];
133+
var num_ok2 = numMap[0];
134+
var num_ok3 = numMap[0];
135+
var num_ok4 = numMap[NumericEnum1.A];
136+
var num_ok5 = numMap[NumericEnum2.A];
137+
// Generics
138+
function generic1(arg) {
139+
// Should error
140+
return arg["blah"];
141+
}
142+
function generic2(arg) {
143+
// Should OK
144+
return arg["blah"];
145+
}
146+
function generic3(arg) {
147+
// Should error
148+
return strMap[arg];
149+
}
150+
obj1["x"];
151+
var y = "y";
152+
obj1[y];
153+
var yy = "y";
154+
obj1[yy];
155+
var z = "z";
156+
obj1[z];
157+
// Should error
158+
var f1 = strMapUnion["foo"];
159+
var e15 = symbolMap[s]; // Should OK
160+
symbolMap[s] = undefined; // Should error

0 commit comments

Comments
 (0)