19

In TypeScript, if strict null checking is enabled, I would expect the compiler to prevent me from assigning null or undefined values to a variable unless it admits null.

However, array access seems to allow circumventing this check.

Example:

let a: string[] = ["Hello"];
let s: string;

// 1) this produces an error, as expected
s = undefined

// 2) s is undefined here, too, but no error
s = a[3];
console.log(s);

Runnable version on the TypeScript Playground (Note: "strict null checking" must be enabled in the "Options" dialog).

What is going on here?

  • Is this a bug in the TypeScript compiler?
  • Or is it a deliberate omission?
  • If the latter, is this documented anywhere (ideally with a rationale why it was done) ?
3
  • 1
    Here you go: github.com/danielnixon/eslint-plugin-total-functions Commented Jun 9, 2020 at 11:57
  • @danielnixon: Interesting - obviously I'm not the only one who noticed. Commented Jun 9, 2020 at 13:50
  • @danielnixon: Consider writing an answer based on your project. It seems to me that using your plugin is a valid solution (or at least workaround) if you want to avoid arrays. Commented Jun 9, 2020 at 13:58

2 Answers 2

23

Found it :-).

tl;dr: It is a deliberate omission. Array access is very common in TypeScript code, and forcing a null/undefined check for every access was considered too cumbersome for developers.

Note that since TypeScript 4.1 this is configurable with the compiler option noUncheckedIndexedAccess. See Klaster_1's answer for details.


The issue has been raised several times in discussions:

The comment on PR 7140 has a nice rationale from Anders Hejlsberg (one of the core developers):

Indexing simply produces a value of the type declared in the matching index signature. Even if it is technically more correct, it would simply be too painful if we automatically added undefined to the type of every indexing operation.

For example, every array element access would have to be accompanied by a non-null guard or a ! assertion. I think that would become hugely irritating.


Personal comment: I think this is a reasonable decision. The problem is inherent with arrays - I don't think it's possible to prove (for the compiler) whether a given position is defined, since you can use calculated positions. So any warning would produce many false positives, and most devs would switch it off anyway.

Essentially, arrays provide more freedom to the developer than the compiler can check. If you want proper checking, the only solution I can see is to avoid direct array access, and use other data structures that provide more protection.

Sign up to request clarification or add additional context in comments.

5 Comments

Very unfortunate
This is really a poor decision for the language IMO. There are so many things that are hugely irritating in Typescript - and for good reason! I will take safe and irritating over unsafe any day.
@AlexNeth: Actually, I think it's a reasonable decision. The problem is inherent with arrays - I don't think it's possible to prove (for the compiler) whether a given position is defined, since you can use calculated positions. So any warning would produce many false positives, and most devs would switch it off anyway. If you want proper checking, you'll have to use some other data structure (e.g. a list that only allows appending, not arbitrary writing).
I feel like foo?[0] would be just fine in places where users want to instruct the compiler it shouldn't try. Or that at least there should be a typescript type of array with strict checking.
the typescript compiler can't know if a position is defined, but eslint's "no unnecessary condition" rule will complain if you check for defined-ness. this is why we can't have nice things
9

TypeScript 4.1 introduced a new compiler option - noUncheckedIndexedAccess. Among other things, it adds undefined to array index access type.

Consider the following snippet (TS playground):

const items = [1,2,3]
console.log(items[1]?.toString(10))
console.log(items[2].toString(10))

Without noUncheckedIndexedAccess, items[2].toString(10) will be deemed valid, and invalid when the option is on. The items[1]?.toString(10) will be valid when the option is on, just like in comment by Alex Neth in the older answer.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.