TypeScript 4.2 beta models fixed arguments after a variable prefix
TypeScript 4.2 beta permits leading and middle tuple rest elements. Alias preservation also keeps more of the author's vocabulary in diagnostics and declaration output.
A JavaScript API can accept an arbitrary number of values followed by one required control argument. Describing that shape with a homogeneous array loses the special meaning of the last position. The TypeScript 4.2 beta announced on January 12 extends tuple rest elements so a declaration can describe that existing calling convention. The improvement concerns the precision of the type assigned to an ordinary argument array.
Earlier tuple rest elements had to occupy the final position. The beta permits a rest element at the beginning or in the middle, with restrictions on what can follow it. A variable region can therefore coexist with a required suffix. This matters for declarations that need to model JavaScript as it exists, without demanding an API redesign before its arguments can be checked.
I remain impressed by the ecosystem built around TypeScript, especially the tools that make web interfaces manageable. Better declarations contribute directly to that usefulness. Their value comes from describing the framework or library's actual semantics closely enough that ordinary usage receives meaningful checking.
A variable prefix can have a fixed ending
A tuple such as [...string[], boolean] contains any number of strings followed by a boolean. Zero strings is valid because the rest portion can be empty. The required suffix still establishes a minimum length and a constraint on the final value.
declare function writeNames(
...args: [...names: string[], uppercase: boolean]
): void
writeNames(false)
writeNames("Ada", "Grace", true)
// Rejected, the final argument must be boolean.
writeNames("Ada", "Grace")
The declaration uses one JavaScript rest parameter. Its tuple type describes how arguments inside that parameter are arranged. This does not introduce new runtime syntax for a leading rest parameter. The distinction lets TypeScript express a shape that the underlying JavaScript parameter syntax does not express directly.
Middle rest elements work similarly. [boolean, ...string[], boolean] has a required value at each end and a variable string region between them. The fixed positions state which arguments have special roles. Replacing the tuple with an array of a union type would permit those value types in positions where this API does not accept them. Tuple labels can explain the positions to readers without requiring the runtime array to store a separate name for each element.
The restrictions keep the variable region interpretable. Another rest element cannot follow it, and an optional element cannot follow it. A required suffix gives a definite number of positions to reserve after the variable region. Allowing an optional suffix would make that division less direct.
An alias carries information for the reader
The same beta also improves type alias preservation. A named union can disappear from a displayed type when the checker combines it with another union. The normalized result still describes the possible values, but its expanded spelling loses the name chosen by the author.
For example, an alias for several primitive types can be preserved when used alone, then expand when a function can also return undefined. The beta announcement explains that union normalization previously discarded information about where those constituent types came from. The resulting editor display could become much larger than the expression the programmer wrote.
The change retains information about the construction of types before normalization. That gives the printer more context when producing declaration files, diagnostics and editor information. A reader can see the author's named grouping even when the checker also needs a normalized representation for its work.
export type SettingValue = string | number | boolean
export function findValue(
value: SettingValue,
present: boolean
) {
return present ? value : undefined
}
With strict null checking, the useful return description is SettingValue | undefined. It retains the grouping that explains what the nonmissing values mean. This improvement concerns how the checker represents and displays equivalent types, so it should not be mistaken for a new runtime representation of the returned value.
Preserving names has an implementation cost
Anders Hejlsberg's implementation discussion describes including alias symbols in the keys used to intern several type forms. Aliased forms can therefore retain distinct internal identities. The discussion also records an origin description for unions when the original construction has fewer constituents than the expanded normalized result.
That preserves information the printer previously could not recover. It also means the compiler can create more internal type identities for equivalent unions. The implementation discussion anticipates a small performance cost, without making a universal claim about compilation times. Readability and normalization therefore require a concrete engineering tradeoff inside the checker.
I prefer diagnostics that retain the vocabulary of the declaration being checked. An expanded union can be accurate while making a failure harder to relate to the source. Keeping the alias available gives the user a shorter route back to the abstraction that defined the values.
The tuple extension and alias preservation improve different aspects of that relationship. A declaration can describe a variable argument prefix more faithfully, and the tools can preserve more of the names used to describe the resulting types. During the beta, library authors can evaluate both the accepted calls and the declarations their consumers will actually read.