TypeScript 2.1 RC follows assignments and lowers async functions
The TypeScript 2.1 release candidate expands inference for incrementally initialized variables and brings async functions to ES3 and ES5 targets.
JavaScript often initializes a value gradually. A variable begins without an initializer, or an array starts empty and acquires elements later. A type checker that decides the type only at that first declaration has little information to use. TypeScript 2.1's release candidate follows subsequent assignments so those familiar JavaScript patterns can retain useful checking without requiring an annotation at every declaration.
Daniel Rosenwasser's November 8 announcement highlights this inference work alongside asynchronous functions targeting ES3 and ES5. The candidate is available through the rc package tag. These are two different compiler changes, one to understanding existing code and one to emitting code for older runtimes. Both reduce restrictions that previously made ordinary JavaScript programming patterns awkward to express in TypeScript.
I am interested in TypeScript chiefly because of the web ecosystem built around it. Checking that follows the way an application is written is useful there, provided the compiler's conclusions remain understandable. Requiring less annotation is beneficial when the compiler still identifies what a particular use of a value is allowed to do.
An uninitialized variable can acquire a useful type
The announcement begins with implicit any. Without enough information, the compiler has used that permissive type to avoid obstructing assignments. Enabling noImplicitAny reports places where it cannot infer a type. The release candidate uses control-flow analysis to derive a more specific type from what happens after the declaration.
Rosenwasser's example assigns a function to an initially uninitialized variable. At that point, the compiler knows the variable can be called and that adding a number to it is erroneous. A subsequent string assignment remains permitted, and later string operations become valid. The declaration does not permanently freeze the variable into the type of its first assignment.
The distinction is between assignment flexibility and checking a particular use. The compiler can permit the variable to change while rejecting an operation incompatible with its current inferred value. That is more informative than treating every use as any. It also reflects code that already exists rather than demanding an up-front declaration of every intermediate state.
The TypeScript 2.0 announcement explains the preceding expansion of control-flow analysis. Version 2.0 used that analysis to determine more specific types at different points and to support checks such as definite assignment. The 2.1 candidate builds on that work by examining assignments to values that would otherwise receive an unhelpfully permissive inferred type.
Empty arrays are another incremental declaration
The candidate applies similar tracking to arrays initially written as []. The announcement's example pushes instances of one class and obtains completion for that class's methods when iterating. Adding an instance of a different class changes what the checker can safely assume about each element. A method present only on the first class is no longer valid for every element.
This matters for migration because JavaScript code commonly builds a collection before consuming it. A useful inferred type should reflect that construction. Treating the empty declaration as the only source of information misses the assignments that explain the collection's eventual contents.
The result affects editor behavior as well as diagnostics. The announcement explicitly connects the inferred element type to completion suggestions. A compiler that knows more about the actual values can offer relevant operations and identify unsupported ones using the same information. Editor assistance and checking therefore depend on a common account of the program's flow.
Async syntax does not supply a Promise implementation
The other main change lowers asynchronous functions for ES3 and ES5 targets. The announcement says earlier support required targeting ES2015. An async function can express sequential asynchronous steps with await, while the compiler emits code suitable for older JavaScript syntax targets.
That transformation does not provide every runtime facility the emitted program needs. The announcement requires an ECMAScript-compatible global Promise, supplied by the runtime or a polyfill. It separately requires declaration information so the checker knows that Promise exists. Its example includes this library configuration.
{
"compilerOptions": {
"lib": ["dom", "es2015.promise", "es5"]
}
}
The configuration tells TypeScript which APIs to recognize. It does not install a promise implementation in a browser. Keeping those responsibilities separate explains how a program can type-check successfully and still require runtime setup before its asynchronous functions work.
The announcement's example returns a promise from a timer-based delay function. Awaiting that result suspends the async function until the timer resolves it. The loop's next iteration consequently follows completion of the previous wait, preserving the sequence expressed by the source program.
I regard that distinction as essential when evaluating the convenience of downlevel compilation. The compiler removes a syntax barrier, while deployment still has to supply the APIs the transformed program calls. The announcement's delayed-loop example illustrates the intended programming benefit. An await inside the loop preserves readable control flow without blocking other code during the delay, and the required Promise support remains an explicit part of the runtime environment.