This feature was introduced way back in TypeScript 2.1 in 2016. The term "evolving any" is not widely used outside the TypeScript compiler itself, but I find it useful to have a name for this unusual pattern.
In TypeScript a variable's type is generally determined when it is declared. After this, it can be refined (by checking if it is
null, for instance), but it cannot expand to include new values. There is one notable exception to this, however, involving
When you convert this to TypeScript, it works exactly as you'd expect:
Upon closer inspection, however, it's surprising that this works! How does TypeScript know that the type of
number when it's initialized as
, which could be an array of any type?
Inspecting each of the three occurrences of
out to reveal its inferred type starts to tell the story:
The type of
out starts as
any, an undifferentiated array. But as we push
number values onto it, its type "evolves" to become
This is distinct from narrowing (Item 22). An array's type can expand by pushing different elements onto it:
With conditionals, the type can even vary across branches. Here we show the same behavior with a simple value, rather than an array:
A final case that triggers this "evolving any" behavior is if a variable is initially
null. This often comes up when you set a value in a
Interestingly, this behavior only happens when a variable's type is implicitly
noImplicitAny set! Adding an explicit
any keeps the type constant:
This behavior can be confusing to follow in your editor since the type is only "evolved" after you assign or push an element. Inspecting the type on the line with the assignment will still show
If you use a value before any assignment to it, you'll get an implicit any error:
Put another way, "evolving"
any types are only
any when you write to them. If you try to read from them while they're still
any, you'll get an error.
any types do not evolve through function calls. The arrow function here trips up inference:
In cases like this, you may want to consider using an array's
filter methods to build arrays in a single statement and avoid iteration and evolving
any entirely. See Items 23 and 27.
any comes with all the usual caveats about type inference. Is the correct type for your array really
(string|number)? Or should it be
number and you incorrectly pushed a
string? You may still want to provide an explicit type annotation to get better error checking instead of using evolving
- While TypeScript types typically only refine, implicit
anytypes are allowed to evolve. You should be able to recognize and understand this construct where it occurs.
- For better error checking, consider providing an explicit type annotation instead of using evolving