Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--noUncheckedIndexedAccess #39560

Merged
merged 11 commits into from Sep 11, 2020
75 changes: 51 additions & 24 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/compiler/commandLineParser.ts
Expand Up @@ -624,6 +624,14 @@ namespace ts {
category: Diagnostics.Additional_Checks,
description: Diagnostics.Report_errors_for_fallthrough_cases_in_switch_statement
},
{
name: "noUncheckedIndexedAccess",
type: "boolean",
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: false,
category: Diagnostics.Additional_Checks,
description: Diagnostics.Include_undefined_in_index_signature_results
},

// Module Resolution
{
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/diagnosticMessages.json
Expand Up @@ -4721,6 +4721,11 @@
"code": 6504
},

"Include 'undefined' in index signature results": {
"category": "Message",
"code": 6800
},

"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Expand Up @@ -5783,6 +5783,7 @@ namespace ts {
assumeChangesOnlyAffectDirectDependencies?: boolean;
noLib?: boolean;
noResolve?: boolean;
noUncheckedIndexSignatures?: boolean;
out?: string;
outDir?: string;
outFile?: string;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Expand Up @@ -2841,6 +2841,7 @@ declare namespace ts {
assumeChangesOnlyAffectDirectDependencies?: boolean;
noLib?: boolean;
noResolve?: boolean;
noUncheckedIndexSignatures?: boolean;
out?: string;
outDir?: string;
outFile?: string;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Expand Up @@ -2841,6 +2841,7 @@ declare namespace ts {
assumeChangesOnlyAffectDirectDependencies?: boolean;
noLib?: boolean;
noResolve?: boolean;
noUncheckedIndexSignatures?: boolean;
out?: string;
outDir?: string;
outFile?: string;
Expand Down
109 changes: 109 additions & 0 deletions tests/baselines/reference/noUncheckedIndexedAccess.errors.txt
@@ -0,0 +1,109 @@
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(3,32): error TS2344: Type 'boolean | undefined' does not satisfy the constraint 'boolean'.
Type 'undefined' is not assignable to type 'boolean'.
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(38,1): error TS2322: Type 'undefined' is not assignable to type 'boolean'.
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(39,1): error TS2322: Type 'undefined' is not assignable to type 'boolean'.
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(40,1): error TS2322: Type 'undefined' is not assignable to type 'boolean'.
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(41,1): error TS2322: Type 'undefined' is not assignable to type 'boolean'.
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(85,1): error TS2322: Type 'undefined' is not assignable to type 'string'.


==== tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts (6 errors) ====
type CheckBooleanOnly<T extends boolean> = any;
// Validate CheckBooleanOnly works - should error
type T_ERR1 = CheckBooleanOnly<boolean | undefined>;
~~~~~~~~~~~~~~~~~~~
!!! error TS2344: Type 'boolean | undefined' does not satisfy the constraint 'boolean'.
!!! error TS2344: Type 'undefined' is not assignable to type 'boolean'.

enum NumericEnum1 { A, B, C }
enum NumericEnum2 { A = 0, B = 1 , C = 2 }
enum StringEnum1 { A = "Alpha", B = "Beta" }

declare const strMap: { [s: string]: boolean };

// All of these should be errors
const e1: boolean = strMap["foo"];
const e2: boolean = strMap.bar;
const e3: boolean = strMap[0];
const e4: boolean = strMap[0 as string | number];
const e5: boolean = strMap[0 as string | 0 | 1];
const e6: boolean = strMap[0 as 0 | 1];
const e7: boolean = strMap["foo" as "foo" | "baz"];
const e8: boolean = strMap[NumericEnum1.A];
const e9: boolean = strMap[NumericEnum2.A];
const e10: boolean = strMap[StringEnum1.A];
const e11: boolean = strMap[StringEnum1.A as StringEnum1];
const e12: boolean = strMap[NumericEnum1.A as NumericEnum1];
const e13: boolean = strMap[NumericEnum2.A as NumericEnum2];
const e14: boolean = strMap[null as any];
Comment on lines +24 to +38
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of these appear to be errors.


// Should be OK
const ok1: boolean | undefined = strMap["foo"];
const ok2: boolean | undefined = strMap.bar;

type T_OK1 = CheckBooleanOnly<(typeof strMap)[string]>;
type T_OK2 = CheckBooleanOnly<(typeof strMap)["foo"]>;
type T_OK3 = CheckBooleanOnly<(typeof strMap)["bar" | "baz"]>;
type T_OK4 = CheckBooleanOnly<(typeof strMap)[number]>;
type T_OK5 = CheckBooleanOnly<(typeof strMap)[any]>;

// Writes don't allow 'undefined'; all should be errors
strMap["baz"] = undefined;
~~~~~~~~~~~~~
!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'.
strMap.qua = undefined;
~~~~~~~~~~
!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'.
strMap[0] = undefined;
~~~~~~~~~
!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'.
strMap[null as any] = undefined;
~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'.

// Numeric lookups are unaffected
declare const numMap: { [s: number]: boolean };
// All of these should be ok
const num_ok1: boolean = numMap[0];
const num_ok2: boolean = numMap[0 as number];
const num_ok3: boolean = numMap[0 as 0 | 1];
const num_ok4: boolean = numMap[NumericEnum1.A];
const num_ok5: boolean = numMap[NumericEnum2.A];

// Generics
function generic1<T extends { [s: string]: boolean }>(arg: T): boolean {
// Should error
return arg["blah"];
}
function generic2<T extends { [s: string]: boolean }>(arg: T): boolean {
// Should OK
return arg["blah"]!;
}
function generic3<T extends string>(arg: T): boolean {
// Should error
return strMap[arg];
}

// Element access into known properties is ok
declare const obj1: { x: string, y: number, [key: string]: string | number };
obj1["x"];
const y = "y";
obj1[y];
let yy = "y";
obj1[yy];
let z = "z";
obj1[z];

// Distributivity cases
declare const strMapUnion: { [s: string]: boolean } | { [s: string]: number };
// Should error
const f1: boolean | number = strMapUnion["foo"];

// Symbol index signatures
declare const s: unique symbol;
declare const symbolMap: { [s]: string };
const e15: string = symbolMap[s]; // Should OK
symbolMap[s] = undefined; // Should error
~~~~~~~~~~~~
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.

160 changes: 160 additions & 0 deletions tests/baselines/reference/noUncheckedIndexedAccess.js
@@ -0,0 +1,160 @@
//// [noUncheckedIndexedAccess.ts]
type CheckBooleanOnly<T extends boolean> = any;
// Validate CheckBooleanOnly works - should error
type T_ERR1 = CheckBooleanOnly<boolean | undefined>;

enum NumericEnum1 { A, B, C }
enum NumericEnum2 { A = 0, B = 1 , C = 2 }
enum StringEnum1 { A = "Alpha", B = "Beta" }

declare const strMap: { [s: string]: boolean };

// All of these should be errors
const e1: boolean = strMap["foo"];
const e2: boolean = strMap.bar;
const e3: boolean = strMap[0];
const e4: boolean = strMap[0 as string | number];
const e5: boolean = strMap[0 as string | 0 | 1];
const e6: boolean = strMap[0 as 0 | 1];
const e7: boolean = strMap["foo" as "foo" | "baz"];
const e8: boolean = strMap[NumericEnum1.A];
const e9: boolean = strMap[NumericEnum2.A];
const e10: boolean = strMap[StringEnum1.A];
const e11: boolean = strMap[StringEnum1.A as StringEnum1];
const e12: boolean = strMap[NumericEnum1.A as NumericEnum1];
const e13: boolean = strMap[NumericEnum2.A as NumericEnum2];
const e14: boolean = strMap[null as any];

// Should be OK
const ok1: boolean | undefined = strMap["foo"];
const ok2: boolean | undefined = strMap.bar;

type T_OK1 = CheckBooleanOnly<(typeof strMap)[string]>;
type T_OK2 = CheckBooleanOnly<(typeof strMap)["foo"]>;
type T_OK3 = CheckBooleanOnly<(typeof strMap)["bar" | "baz"]>;
type T_OK4 = CheckBooleanOnly<(typeof strMap)[number]>;
type T_OK5 = CheckBooleanOnly<(typeof strMap)[any]>;

// Writes don't allow 'undefined'; all should be errors
strMap["baz"] = undefined;
strMap.qua = undefined;
strMap[0] = undefined;
strMap[null as any] = undefined;

// Numeric lookups are unaffected
declare const numMap: { [s: number]: boolean };
// All of these should be ok
const num_ok1: boolean = numMap[0];
const num_ok2: boolean = numMap[0 as number];
const num_ok3: boolean = numMap[0 as 0 | 1];
const num_ok4: boolean = numMap[NumericEnum1.A];
const num_ok5: boolean = numMap[NumericEnum2.A];

// Generics
function generic1<T extends { [s: string]: boolean }>(arg: T): boolean {
// Should error
return arg["blah"];
}
function generic2<T extends { [s: string]: boolean }>(arg: T): boolean {
// Should OK
return arg["blah"]!;
}
function generic3<T extends string>(arg: T): boolean {
// Should error
return strMap[arg];
}

// Element access into known properties is ok
declare const obj1: { x: string, y: number, [key: string]: string | number };
obj1["x"];
const y = "y";
obj1[y];
let yy = "y";
obj1[yy];
let z = "z";
obj1[z];

// Distributivity cases
declare const strMapUnion: { [s: string]: boolean } | { [s: string]: number };
// Should error
const f1: boolean | number = strMapUnion["foo"];

// Symbol index signatures
declare const s: unique symbol;
declare const symbolMap: { [s]: string };
const e15: string = symbolMap[s]; // Should OK
symbolMap[s] = undefined; // Should error


//// [noUncheckedIndexedAccess.js]
"use strict";
var NumericEnum1;
(function (NumericEnum1) {
NumericEnum1[NumericEnum1["A"] = 0] = "A";
NumericEnum1[NumericEnum1["B"] = 1] = "B";
NumericEnum1[NumericEnum1["C"] = 2] = "C";
})(NumericEnum1 || (NumericEnum1 = {}));
var NumericEnum2;
(function (NumericEnum2) {
NumericEnum2[NumericEnum2["A"] = 0] = "A";
NumericEnum2[NumericEnum2["B"] = 1] = "B";
NumericEnum2[NumericEnum2["C"] = 2] = "C";
})(NumericEnum2 || (NumericEnum2 = {}));
var StringEnum1;
(function (StringEnum1) {
StringEnum1["A"] = "Alpha";
StringEnum1["B"] = "Beta";
})(StringEnum1 || (StringEnum1 = {}));
// All of these should be errors
var e1 = strMap["foo"];
var e2 = strMap.bar;
var e3 = strMap[0];
var e4 = strMap[0];
var e5 = strMap[0];
var e6 = strMap[0];
var e7 = strMap["foo"];
var e8 = strMap[NumericEnum1.A];
var e9 = strMap[NumericEnum2.A];
var e10 = strMap[StringEnum1.A];
var e11 = strMap[StringEnum1.A];
var e12 = strMap[NumericEnum1.A];
var e13 = strMap[NumericEnum2.A];
var e14 = strMap[null];
// Should be OK
var ok1 = strMap["foo"];
var ok2 = strMap.bar;
// Writes don't allow 'undefined'; all should be errors
strMap["baz"] = undefined;
strMap.qua = undefined;
strMap[0] = undefined;
strMap[null] = undefined;
// All of these should be ok
var num_ok1 = numMap[0];
var num_ok2 = numMap[0];
var num_ok3 = numMap[0];
var num_ok4 = numMap[NumericEnum1.A];
var num_ok5 = numMap[NumericEnum2.A];
// Generics
function generic1(arg) {
// Should error
return arg["blah"];
}
function generic2(arg) {
// Should OK
return arg["blah"];
}
function generic3(arg) {
// Should error
return strMap[arg];
}
obj1["x"];
var y = "y";
obj1[y];
var yy = "y";
obj1[yy];
var z = "z";
obj1[z];
// Should error
var f1 = strMapUnion["foo"];
var e15 = symbolMap[s]; // Should OK
symbolMap[s] = undefined; // Should error