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 any
types.
In JavaScript, you might write a function to generate a range of numbers like this:
|
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 out
is 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 number[]
.
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 try
/catch
block:
|
Interestingly, this behavior only happens when a variable's type is implicitly any
with 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 any
or any[]
.
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.
Implicit 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 map
and filter
methods to build arrays in a single statement and avoid iteration and evolving any
entirely. See Items 23 and 27.
Evolving 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 any
.
Things to Remember
- While TypeScript types typically only refine, implicit
any
andany[]
types 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
any
.