TypeScript 4.2 retains more of the program's declared structure
TypeScript 4.2 improves tuple declarations and preserves aliases through more type operations. Its property-access option and file explanations also expose distinctions that were previously easy to miss.
A compiler can know which values a type accepts while losing the grouping that made the declaration understandable. Flattening a named union into all of its constituent types preserves its possibilities but discards part of its explanation. TypeScript 4.2, released on February 23, retains more of that information. The resulting diagnostics and declaration files can preserve names that correspond to the author's intended concepts.
The release also extends tuple rest elements to describe variable argument regions before required positions. Both changes help the type system describe existing code without losing structure that matters to readers. A more precise declaration is useful only if the compiler can also present it intelligibly when something fails.
I am impressed by the quality of the web ecosystem around TypeScript, although the language still requires careful attention to its rules. These changes address a practical part of that work. They improve the connection between the interfaces a library author writes and the information a consumer sees in an editor.
Normalization can erase the useful name
Consider an alias representing a family of values. A function can return that family or indicate that no value exists. Earlier versions could preserve the alias when used alone but expand it after combining it with undefined.
export type Setting = string | number | boolean
export function lookup(value: Setting, found: boolean) {
if (!found) {
return undefined
}
return value
}
With strict null checking, a concise return description is Setting | undefined. The fully expanded union describes the same alternatives, but it omits the named grouping. For larger unions of object types, that omission can make diagnostics lengthy and harder to connect to a declaration.
The release announcement explains that normalization flattens unions, which previously lost their construction history. Recovering an alias afterward would require considering possible groupings and deciding among aliases that might describe the same constituents. Keeping the original information avoids asking the printer to reconstruct that intention from the final normalized result.
Anders Hejlsberg's implementation discussion describes including alias symbols in internal type-interning keys. It also retains origin descriptions for certain unions. This creates more internal identities for equivalent types, with an anticipated performance cost. The change deliberately preserves information for diagnostics and declarations even when those types have equivalent accepted values.
That is an internal identity used by the compiler. It should not be read as a claim that naming an ordinary alias creates a distinct runtime value or automatically makes the language nominally typed. The advertised result is better preservation of the spelling and construction of types across operations that previously expanded them.
A required suffix can follow a variable region
Tuple types describe arrays whose positions have particular meanings. Before this release, a tuple rest element had to be last. TypeScript 4.2 permits leading and middle rest elements, provided another rest element or an optional element does not follow one.
type NamedRequest = [...names: string[], urgent: boolean]
declare function submit(...request: NamedRequest): void
submit(false)
submit("invoice", true)
submit("invoice", "receipt", false)
The rest region can contain no strings, so the first call is valid. Every call still needs the trailing boolean. An array of string | boolean would fail to express that final-position requirement and would allow combinations this declaration rejects.
A middle rest element can likewise leave fixed positions at both ends. For an established JavaScript API, this lets its declaration reflect how callers already arrange the arguments. The function still receives arguments through one ordinary rest parameter. TypeScript's tuple annotation describes their arrangement without adding a new runtime parameter syntax.
The restrictions on trailing optional or additional rest elements limit ambiguity in dividing the positions. A required suffix has a known size, so the variable region occupies the positions left before it. This is particularly useful for declaration authors because a precise signature can replace a permissive array type that otherwise loses the ordering contract.
The release also puts a limit on tuple expansion through spreads. Its announcement explains that very large generated tuples can make checking take too long, including in editors. More expressive type construction therefore comes with a compiler bound intended to prevent accidental expansion from making the checking process hang.
Declared properties differ from arbitrary keys
An index signature lets an object type accept keys beyond its explicitly declared properties. That is useful for dictionaries and extensible option objects. It can also hide a spelling mistake, because a misspelled declared property can appear to be a valid arbitrary key.
TypeScript 4.2 adds noPropertyAccessFromIndexSignature. When enabled, a dotted access must correspond to a declared property. Access through the index signature uses brackets. The release leaves this option outside the strict family because its usefulness depends on the codebase's conventions.
interface Options {
timeout: number
[name: string]: number
}
declare const options: Options
options.timeout
options["custom"]
// With noPropertyAccessFromIndexSignature, this is rejected.
options.timout
This does not remove the ability to access arbitrary keys. It makes that choice visible in the expression. The misspelled dotted access can now fail even though the index signature would otherwise admit that name.
The distinction is valuable for option objects that mix known fields and extensions. A reader can see which access relies on a specific declaration and which relies on the broader dictionary contract. It also gives the checker a way to catch certain mistakes without requiring the library to prohibit every additional property.
I prefer that explicit distinction when an object has a documented set of normal properties. It makes accidental spelling errors less likely to become accepted dictionary lookups. An application built primarily around arbitrary keys may make a different choice, which is why the opt-in behavior matters.
A project needs an explanation of its files
The compiler's view of the program includes files selected through several mechanisms. A developer can see a diagnostic in a dependency or a library declaration without understanding how that file entered the compilation. Editing an exclude pattern does not itself explain the dependency relationships that led to inclusion.
The new --explainFiles option reports reasons for including files. The release announcement shows library references and a local file matched by a configuration pattern. This converts an inferred project boundary into output that a developer can inspect. It is useful when the observed program differs from the collection of files the developer expected.
The announcement does not guarantee a stable output format. Human inspection is therefore the immediate contract. An automated consumer that assumes a permanent layout would rely on something the release explicitly declines to promise.
This is separate from checking whether the code inside a file is valid. A diagnostic can be correct for a file that entered the program unintentionally. Explaining inclusion helps establish whether the compiler is evaluating the intended project before the developer tries to resolve the reported type error.
A constructor type can permit extension without instantiation
Abstract construct signatures distinguish another pair of operations that previously shared an overly restrictive description. A parameter typed as an ordinary constructor must be callable with new. That excludes an abstract class, even when the receiving function only intends to extend it in a mixin factory.
The new abstract new form permits abstract constructors without allowing the consumer to assume direct instantiation is valid. Concrete classes can still satisfy the annotation. The type states a capability restriction on what the receiving code may do, while accommodating a broader set of inputs.
The release describes a corresponding rule for generic mixins. A class extending a generic value bounded by an abstract constructor must itself be abstract. Its implementation cannot know every abstract member that a supplied base class may require. Marking the generated class abstract preserves that obligation for the eventual concrete subclass.
That rule prevents a factory's convenience from erasing requirements established by the base class. The compiler can support composition while keeping the distinction between obtaining a class and obtaining one that is ready to instantiate.
The stable release also narrows a beta experiment
The 4.2 beta experimented with assigning template literal types to template string expressions before widening. The stable announcement says that behavior was reverted after feedback. A const binding alone therefore should not be treated as proof that an interpolated expression retains a template literal type in this release.
An explicit as const can request literal-like inference for such an expression. This distinction matters when reading beta examples, because an expression's inferred type can affect its compatibility with a declaration even though the emitted string has the same runtime content. The stable release documentation is the appropriate reference for the compiler being adopted.
These changes make several compiler decisions more visible. Required tuple positions can remain explicit around a variable region, and aliases can survive into the information presented to users. When a problem instead concerns an unknown property or an unexpected source file, the release supplies a separate way to expose the assumption responsible for that behavior.