TypeScript 4.8 makes narrowing and inference use stronger evidence
TypeScript 4.8 simplifies non-nullable generics and extracts literal types from template strings. Its correctness changes also remove inference that could invent information from a binding pattern.
A type checker needs to preserve information established by a program without inventing facts the program never supplied. TypeScript 4.8 changes both sides of that obligation. A generic null check now produces a more useful type, while a destructuring pattern loses the power to manufacture an inferred result that an uncalled argument never justified.
The August 25 release announcement groups these changes with intersection reduction and literal inference in template-string types. They affect ordinary helper functions as well as type-level utilities. The connection is the evidence used to justify the compiler's conclusion.
I value TypeScript when its analysis follows the code a maintainer can inspect. A null check should establish absence of null. Destructuring a result should not establish that a function somehow created an array. This release improves that correspondence in ways that can remove assertions and introduce useful errors.
NonNullable acquires a reducible representation
Under strict null checking, the empty object type literal accepts values other than null and undefined. It does not restrict a value to a conventional object instance. Intersecting a generic type with that category therefore represents its non-nullish part.
The release changes NonNullable<T> from a conditional type to T & {}. Its implementation discussion explains how this supports control-flow analysis and repeated reduction. A non-nullish intersection can simplify in cases where the previous conditional representation remained difficult to relate.
That matters when generic utilities compose. Applying the same non-nullable operation twice should not create a result that cannot be assigned back to the result of one application. The new definition gives the checker a more direct way to express their compatibility.
The implementation includes deliberate exceptions for explicitly written primitive intersections used by libraries for completion behavior. It should not be summarized as erasing every occurrence of empty braces from every intersection. The generic non-nullable operation has a clear purpose, while compatibility requirements preserve some special written forms.
A check supplies the generic constraint locally
When control flow excludes null and undefined, the checker can now narrow a generic value to its intersection with empty braces. That matches the new utility definition. A function can express the runtime requirement and return a non-nullable value without asserting the result type.
function present<T>(value: T): NonNullable<T> {
if (value === null || value === undefined) {
throw new Error("A value is required")
}
return value
}
The function returns the original value. The intersection is a type description, not an object wrapper or conversion performed during execution. Its usefulness comes from representing the branch's evidence in a form that the checker can compare with the declared result.
The explicit predicate also preserves zero and the empty string. A truthiness test would reject them at runtime even though they are non-nullish. Narrowing capability does not decide which predicate matches the application's domain. That remains part of the helper's behavior.
The release also permits assignment from unknown to a union containing empty braces, null and undefined. Its rationale is that this union represents the same broad categories of values. Truthy branches involving unknown now narrow more consistently with that representation.
This is useful but limited evidence. Establishing that a value is non-nullish does not establish its fields or methods. A program still needs another check before treating an arbitrary unknown value as a particular object shape. The release improves one established fact without supplying a complete schema.
Unconstrained does not imply non-nullish
A generic parameter without a constraint can contain null or undefined. Older compatibility behavior allowed some uses of such parameters in positions that excluded those values. TypeScript 4.8 reports those cases under strict null checking.
The announcement shows a generic helper passing its argument to a function expecting a non-nullish value. A caller can instantiate the generic with undefined, so the old acceptance hid a real missing premise. The stricter diagnostic identifies the mismatch at the point where the helper forwards the value.
A generic constraint is appropriate when callers must supply only non-nullish inputs. A runtime check is appropriate when the helper accepts wider inputs and establishes presence itself. Both can repair the error, but they express different API responsibilities.
I prefer making that choice explicit. Adding an assertion solely to preserve a previously accepted call leaves the same uncertainty about who rejects absence. The new diagnostic gives the author a reason to locate that policy in the interface or in executable validation.
Template-string inference can retain literal values
The release also improves constrained infer variables inside template-string types. When the constraint is a primitive type, the checker attempts to infer a literal value from the text. A numeric string can consequently yield a numeric literal type instead of only the broad number type.
type NumericText<S extends string> =
S extends `${infer Value extends number}` ? Value : never
type Hundred = NumericText<"100">
type DecimalSpelling = NumericText<"1.0">
In this release, the first result is the literal type 100. The second is number, because the representation does not round-trip to the same text. The announcement explains that converting the parsed value back to a string produces a spelling that differs from the original input.
This round-trip requirement prevents the checker from claiming a literal representation more specific than its conversion model supports. The change also covers constrained boolean and bigint inference. It is a type-level inference rule, not a new runtime parser supplied to the application.
The implementation discussion identifies converting numeric tuple-index strings to numeric literal equivalents as its main motivation. The general constrained-template approach also handles other primitive placeholders. That supplies a concrete use case for the feature beyond writing elaborate type-level string puzzles.
The precision belongs to an input whose text is known at the type level. An arbitrary runtime string does not become a validated number merely because this utility exists. A library's runtime parsing behavior still needs to agree with whatever relationship its types claim.
Binding patterns stop inventing candidates
The announcement describes a contrasting inference repair. TypeScript previously used a destructuring binding pattern as a low-priority candidate for a generic type argument. A tuple-shaped binding could help the checker recognize that supplied array literals should be treated as tuples.
That useful hint became misleading when a generic function had no argument supplying the result type. Destructuring its return into several variables could make the checker infer a tuple of any values. The receiving pattern was effectively being treated as evidence that the function returned the required structure.
The runtime cannot observe the caller's binding pattern and manufacture that structure. TypeScript 4.8 therefore stops using binding patterns as direct candidates for type arguments. They can still guide a more specific interpretation of a parameter, preserving the useful hint where actual input exists.
This is an example of reduced inference producing a safer result. A missing candidate should remain missing instead of being replaced by any values supplied by a syntactic expectation. An explicit type argument is available when the API genuinely requires the caller to select the type.
The distinction is relevant to declaration authors as well as callers. A signature promising an unconstrained generic return without an input establishing it already needs scrutiny. More eager inference can make such an interface convenient while hiding the absence of runtime evidence for the selected result.
An additional declaration check targets destructuring aliases that look like annotations. A parameter written with a name property renamed to string is not declaring that property's type. JavaScript binding syntax gives the colon a renaming meaning there. The release reports unused aliases in type signatures, directing authors toward a separate object annotation. This catches a case where the source appears typed to a casual reader while the syntax actually expresses a different operation. The diagnostic depends on interpreting the declaration's grammar before drawing conclusions about its intended types.
Compiler correctness includes its view of files
The release also repairs file watching on inode-based systems. Reusing a watcher because a path still exists can be wrong when a save operation or branch change replaces the file at that path. The watcher may remain attached to the old inode.
The announcement credits investigation by Marc Celani and his Airtable team. It installs an appropriate watcher for the new file, addressing stale diagnostics that could otherwise require restarting the compiler or editor. The type analysis cannot produce current answers if its source view is stale.
That operational repair has a different mechanism from intersection reduction, but a similar consequence for trust in diagnostics. The compiler needs valid source inputs and valid reasoning about those inputs. TypeScript 4.8 improves the former through file-watching changes and the latter through narrower inference rules, with each change addressing a specific reason the displayed answer could diverge from the program.