TypeScript 5.6 checks suspicious expressions and iterator completion
TypeScript 5.6 catches conditions whose syntax reveals an unintended result and improves iterator return checking. Its build and editor changes also separate useful early output from completed checking.
A condition can be valid JavaScript and still reveal a likely mistake before any runtime values are known. A regular-expression object in an if does not perform a match. An arrow function where a comparison was intended produces a truthy function object. TypeScript 5.6, released September 9, diagnoses several of these cases by examining the expression's syntax.
The release also improves how built-in iterator results describe completion. Both changes address programs that previously passed checking despite containing information sufficient to expose a mistake. One uses syntax to identify an unintended condition. The other preserves an iterator's completion state instead of allowing an any default to obscure it.
I value TypeScript when its diagnostics explain a concrete mismatch in the program. The web ecosystem already asks for substantial attention to framework behavior and configuration. A compiler that catches a mistaken operator or an unchecked iterator result can remove a useful class of errors without requiring an elaborate new abstraction. The explanation should remain close to the expression that failed.
A predictable condition can reveal the wrong operation
The new checks include regular-expression literals used as conditions and arrow functions that appear where a comparison was likely intended. A regular expression needs a matching operation such as .test. Writing its object literal directly supplies an object, which is truthy. Replacing >= with => similarly changes a comparison into function construction.
Nullish coalescing creates a less visually obvious example. In value < maximum ?? fallback, the comparison is evaluated as the left operand. The result is a boolean, so the fallback cannot handle a missing maximum as the author may have intended. An illustrative correction puts the fallback on the value being compared:
function withinLimit(value: number, maximum: number | undefined) {
return value < (maximum ?? 100);
}
This example chooses a default maximum before comparing. It expresses a different operation from applying nullish coalescing to the comparison result. The diagnostic is useful because it directs attention to precedence and operand placement. Adding a type assertion would not repair the underlying operation.
The checks deliberately permit certain constants, including true, false, zero, and one. An intentional infinite loop with an internal break remains a normal construct. The release also notes that its checks do not exactly overlap ESLint's constant-expression rule. The compiler supplies a particular set of diagnostics rather than claiming to replace every related lint rule.
This scope matters when interpreting success. A program without one of these diagnostics has avoided the recognized suspicious forms. It has not proven that every condition can vary or that every fallback is meaningful. The feature captures useful errors through syntactic evidence, without becoming a general proof system for application control flow.
Iterator completion is a different kind of value
Calling .next() produces a result whose done property distinguishes a yielded element from completion. The yielded value and final return value can have different types. A generator may yield strings and return a numeric summary. Consumers that ignore done cannot safely assume every value is one of the yielded strings.
The historical return-type default of any weakened that distinction. Combining an element type with any produces any, so reading an unchecked result can lose the element type too. TypeScript 5.6 introduces BuiltinIteratorReturn for built-in iterator declarations. Under strictBuiltinIteratorReturn, including through strict, it becomes undefined. The result can then expose the completion case to ordinary checking.
An example handles the state before using the element:
const next = new Set(["ready", "busy"]).values().next();
if (!next.done) {
console.log(next.value.toUpperCase());
}
This guard is meaningful even when the collection's element type is string. The protocol permits completion, and the result tells the consumer whether that happened. The stronger built-in return type avoids using a permissive default to suppress that fact. User-authored iterator signatures can also make their return type explicit when they need a more specific contract.
Helper-capable iterators receive a separate type
The release's published iterator declarations describe IteratorObject and helper methods including map, filter, and take. A mapped result remains an iterator. A consumer can therefore transform values without requiring an intermediate array at each operation. Predicate-aware filtering can narrow the result's element type using the same kind of relationship useful in array filtering.
This is a distinction from the minimal Iterator protocol. An object with a next method can satisfy iteration requirements without inheriting the runtime helper methods. Iterator.from provides an adaptation route for existing iterators and iterables. The declarations need to describe both the protocol and the richer objects without assuming every protocol implementation already has those methods.
The stable type name differs from July's beta, which used BuiltinIterator. TypeScript 5.6 renames it to IteratorObject, adjusts type parameters, and provides specific types such as MapIterator. This is a concrete reason to inspect the stable declarations when publishing code that used prerelease names. The runtime still needs helper support or a polyfill. Compiler declarations alone do not add methods to an older engine.
Checking and emission can run as separate phases
The release exposes noCheck on the command line. It allows emitting output while avoiding a full type-checking pass. A workflow can use tsc --noCheck for output generation and tsc --noEmit for checking. These operations establish different results, so successful emission must not be described as a successful type check.
Declaration generation makes the limit more specific. With source satisfying isolatedDeclarations, emission can rely on local syntactic transformations. Without those annotations, the compiler may still calculate types needed for unannotated declarations. The option therefore avoids a full check without promising that every declaration-emission path performs no semantic work.
This separation lets tooling choose when users receive different kinds of feedback. A developer can obtain runnable output before the entire program's types have been checked. A publishing process can still require the independent check before accepting that output. The release makes those phases available without determining the release policy for every project using them.
Build mode also changes its treatment of errors in dependency projects. It continues through intermediate errors and produces best-effort outputs, allowing downstream work to proceed during a coordinated upgrade. The new stopOnBuildErrors option restores a stopping policy for users who need it. Continuing the build does not erase the upstream diagnostics or establish that the resulting dependency graph is valid.
Side-effect imports receive another optional check through noUncheckedSideEffectImports. Previously, TypeScript could silently ignore an unresolved import that bound no values. That accommodated asset loaders but also concealed misspelled module paths. The new option reports unresolved side-effect imports, making the intended dependency visible to checking even though the source imports no named value.
Asset modules can use ambient declarations, including a wildcard declaration for CSS. That tells the checker a category of module is intentionally provided by another part of the toolchain. It does not inspect every matching asset on disk. The distinction matters because allowing a category and proving the existence of an individual file are different guarantees. The option improves missing-module feedback while leaving asset-processing behavior with the bundler or loader responsible for it.
An editor can receive regional feedback before whole-file results
Region-prioritized diagnostics allow an editor to request attention to a relevant part of a large file. The service can return diagnostics for that region and later return a whole-file result. This prioritizes the feedback needed for the current edit without redefining a regional result as completion of the entire file's check.
The release reports an example using TypeScript's own checker source. Its full semantic diagnostic response took 3,330 milliseconds, while the first regional response took 143 milliseconds. The remaining whole-file response still took roughly 3,200 milliseconds. Those numbers describe the tested file and setup, but they explain the difference between time to useful local feedback and total checking work.
The implementation also addresses error-message consistency under lazy, out-of-order checking. Cached type comparisons could otherwise produce different messages depending on what was checked earlier. Prioritizing a region makes that problem more visible, so preserving useful diagnostics requires work beyond merely checking fewer lines first. The service needs both an early answer and a coherent later answer.
I regard that distinction as consistent with the release's other improvements. A syntax diagnostic can identify a mistake immediately, while full checking still has its own responsibilities. An emitted file can be useful before it is accepted as checked. An iterator result must identify whether it contains an element or completion. TypeScript 5.6 makes these states more explicit, allowing the caller or tool to decide which result is sufficient for its next operation.