Home > Blockchain >  In TypeScript, why is an empty array inferred as "any[]" when noImplicitAny is true, and i
In TypeScript, why is an empty array inferred as "any[]" when noImplicitAny is true, and i

Time:06-18

let a = [];
a.push(0);

The code above compiles fine when

  1. both noImplicitAny and strictNullChecks are set to true
  2. both noImplicitAny and strictNullChecks are set to false

But it issues an error on the second line when

  1. noImplicitAny is false and strictNullChecks is true

The error issued is Argument of type 'number' is not assignable to parameter of type 'never'.

It seems that in the first two cases a is being inferred as a any[], and in the last case it is inferred as a never[], but why does this happen? If noImplicitAny is false, shouldn't it be able to still infer it as any[]? Conversely, why is it being inferred as any[] in the first case where noImplicitAny is true? It seems that the expected behaviors are reversed somehow...

Check the images attached below for the errors and options used.

First two cases: enter image description here

Last case: enter image description here

CodePudding user response:

This issue was raised in microsoft/TypeScript#36987. The authoritative (but not particularly informative) answer there is that this behavior is as intended, and required for backward compatibility. Generally speaking TypeScript doesn't introduce breaking changes unless these changes improve many more things than they break.


When the --noImplicitAny compiler option and the --strictNullChecks compiler option are both enabled, then empty arrays are not really of type any[]; instead they are "auto" or "wildcard" or "evolving" types that change depending on what values are observed to be added to them. This is implemented in microsoft/TypeScript#11432:

let a = []; // auto-typed
a.push(0); // now a is seen as number[]
a.map(x => x.toFixed()) // okay
a.map(x => x.toUpperCase()) // error, numbers don't have a toUpperCase() method

When --noImplicitAny is off, this doesn't happen, since doing so would have added new errors to existing code which used to be "fine" (if a is of type any[], then x in the map() method should be of type any, and so no error should occur on x.toUpperCase() or x.toFaxed() or anything).


As for the difference between --strictNullChecks being on or off, there were several bug fixes having to do with empty array literals interacting badly with other use cases; see microsoft/TypeScript#19576 and microsoft/TypeScript#19745. These fixes changed the behavior when --strictNullChecks is on, but not when it is off, again to preserve backward compatibility in these other use cases.


So that's what's going on, more or less. You're seeing the consequences of several features and bug fixes interacting in a peculiar way in the face of certain compiler options.

Pragmatically speaking you should always use the full --strict suite of compiler options where possible. This gives you a "standard" amount of type safety and is used widely so there's a large amount of documentation and discussion in the community. If you selectively disable some compiler options and you run into something strange or unexpected, there are fewer resources available to you, because you might be among very few people with such a configuration. And even if you find a genuine language bug, there will be less pressure for it to be fixed, so it might persist for a long time or forever.

Playground link to code

CodePudding user response:

This is intended behaviour as it can be observed here.

It has mostly to do with backwards compatibility. The main gist is something along these lines

  • the type for the array is actually undefined[] that gets widened to any[]
  • but when strictNullChecks is true then undefined is not an option so it gets updated to never[]
  • Related