AL+

Language: RO (română) EN (English)

Email us: paul@alplustech.com

Looking back at the week of 26 February–4 March 2025

Check the return branch before any erases the evidence

TypeScript 5.8 checks conditional return branches against the declared result. Fix incompatible paths without hiding them behind broad type assertions.

Citiți articolul în română

A function promising a particular result should honour that promise on the cache miss as well as the cache hit. TypeScript 5.8 made that obligation easier to check when an any-valued branch had previously hidden an incompatible sibling. The useful response to the new diagnostic is to repair the path that breaks the declared return contract, rather than assert that the whole expression already meets it.

This November 2026 retrospective concerns the 28 February 2025 release. The narrower branch checks had also appeared in the 13 February release candidate announcement. Broader work on conditional return types was pulled back from that release. Treating every conditional-type improvement discussed around the beta as a shipped 5.8 feature would overstate the historical change.

Combining branches can destroy information

The release notes illustrate a function declared to return URL. One conditional branch reads from an untyped cache; the other returns the original string instead of constructing the missing object. The first branch has type any, and the second has type string. When the expression's union becomes any, the incompatible string is no longer visible to the ordinary assignability check against URL.

That mechanism is more specific than saying the compiler became stricter. The implementation already contained a branch whose known type contradicted the declared result. A permissive sibling caused that evidence to disappear before the complete expression was compared with the contract. The 5.8 check examines the branches more granularly against the expected return type, allowing that contradiction to be reported.

This matters because the cache-hit path can make manual inspection misleading. A reviewer may see that the function usually retrieves an object and miss the path that has to create one. The return annotation records what every caller is entitled to expect. Each path through the implementation should support that expectation, including the path taken when stored data is absent.

The compiler improvement still leaves a boundary. A value typed as any is not proved to be a valid URL merely because the known string branch now receives a diagnostic. Fixing one incompatible branch does not validate the cache contents. The new check is useful precisely when its scope remains clear.

Repair the implementation before changing the promise

If the intended behaviour is to construct an object on a miss, construct that object. If the product genuinely permits more than one result shape, describe that in the return type and update callers to handle the alternatives. These are different design decisions. Widening the annotation solely to silence an error can move an accidental implementation detail into the public interface.

A type assertion around the complete return expression offers another escape. It can make the code appear compatible while retaining the branch that violates the promise. That changes the evidence available to the next reviewer: the code now contains an explicit instruction to trust the author at the exact point where the compiler found a contradiction.

The practical cost is delayed discovery. A caller may attempt an operation that makes sense for the promised object and encounter a string instead. In a business application, that can place the failure farther from the cache miss that caused it. This is an illustrative failure route, not a report of an incident or a measured reduction in support work from upgrading TypeScript.

For a review, identify the actual return contract first. Then inspect how each branch obtains or constructs its value. Narrow uncertain inputs through checks that establish the properties the next operation requires. A return annotation is most useful when it concentrates a stable promise, rather than merely restating whatever values the current implementation happens to produce.

Assertions can belong at a validated boundary

The strongest counterargument is that external data and library types do not always arrive with declarations that express what the application has established. A justified assertion can connect runtime validation with a static interface when the compiler cannot represent that connection adequately. Banning assertions altogether would confuse a tool with the evidence supporting its use.

The boundary needs to remain inspectable. State what validation happened, which property it establishes and what the assertion claims. If validation only establishes that a field exists, an assertion claiming a complete complex object reaches farther than the evidence. An assertion can record a justified limit of the type system; it should not replace the missing validation itself.

This distinction also keeps the review proportionate. An assertion at a carefully checked external boundary deserves a different discussion from one added inside ordinary control flow to suppress an incompatible return branch. The former may express knowledge established elsewhere. The latter may simply conceal a mistake the implementation can fix directly.

Keep neighbouring compiler features separate

These return checks do not concern whether TypeScript syntax can be erased for execution. The erasableSyntaxOnly option addresses that separate toolchain boundary. They also differ from diagnostics for expressions that are always truthy. A valid conditional can choose between incompatible results; the condition itself need not be suspicious for the return contract to be broken.

A useful upgrade review therefore groups new diagnostics by the promise they protect. For this one, inspect an affected function and its callers, repair the known incompatible path and examine any remaining untyped boundary separately. Do not claim that a clean compiler run proves the runtime validity of unvalidated inputs.

For custom software development, ask for one important return contract to be traced across both the ordinary path and the fallback path. The acceptance question is concrete: does each path produce what callers were promised? TypeScript 5.8 helps expose an answer that any previously obscured. The implementation still has to supply the correct result.