-
Notifications
You must be signed in to change notification settings - Fork 13k
Description
Suggestion
π Search Terms
noUncheckedIndexAccess, narrowing, assignment, definite assignment, control flow
β Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
β Suggestion
It'd be great if noUncheckedIndexAccess
understood when you have just initialized a given index in terms of control flow, the same way that TS can see that a standalone optional binding is definitely initialized.
π Motivating Example
const stringNumDict: Record<string, number> = Object.create(null);
function isNonZero(key: string, dict: Record<string, number>): boolean {
if (!dict[key]) {
dict[key] = 0;
}
return dict[key] > 0;
}
Here, isNonZero
has to insert a non-null assertion operator on the final line of the function to type check. (Mind, this is a bad pattern; it should be initialized elsewhere. I have seen this come up in the real world in things like a reduce
building a nested map, though.) For a standalone value with the same semantics as dict[key]
, i.e. number | undefined
, the assignment informs TS that it is no longer | undefined
, so the final check works.
π» Use Cases
In the simplest cases (with value types), there is no workaround other than using a non-null assertion operator or an explicit cast. In cases with reference types (e.g. assigning an object if the value isn't set), doing a check like this can suffice as a workaround:
let nested = dict[key];
if (!nested) {
nested = dict[key] = {};
}
nested.otherStuff = ...;