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
anyandany[]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.