Why won't a Boolean cast narrow type in short-circuit evaluation?

I’m trying to narrow an optional argument in short-circuit evaluation.

Here’s a Typescript playground with the below code: https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABFApgZygCgA5zWmAIwBsBPAVTABMVgYwUqB+ALkQwCd6BzASkQDeAWABQiRAHoJiAO4ALFEghwQxKogAGufETKUadBlQ2IEyBYi7c5URHICG1djBqJCKROAP1GTUeI4UKBAOJAAhODhiFEccPAISCmpaHyp+ADJ0xG0EvWTDRgA6aLBuKDkAblEAXyA

function test(possiblyUndefined?: string) {
  // when could `possiblyUndefined` on the right hand side be undefined?
  return Boolean(possiblyUndefined) && possiblyUndefined.length;

This errors out with: 'possiblyUndefined' is possibly 'undefined'.

…But that can’t be right. The left-hand side of the statement can only ever succeed when possiblyUndefined is not undefined, right?

>Solution :

Because the Boolean function will happily accept undefined, and TypeScript doesn’t have it defined as a type predicate that guarantees its input isn’t undefined (probably because that’s not its purpose, even though it’s true that Boolean will return false for undefined). If you look at the current definition of it, it’s this (relevant portion highlighted):

interface BooleanConstructor {
    new(value?: any): Boolean;
    <T>(value?: T): boolean;             // <====
    readonly prototype: Boolean;

declare var Boolean: BooleanConstructor;

That’s just a function that returns boolean, not a type predicate

If you want to handle undefined, you can do so explicitly. I’d also use > 0 so your function always returns boolean, not false | number (though that’s up to you, of course):

return typeof possiblyUndefined !== "undefined" && possiblyUndefined.length > 0;
// or
return possiblyUndefined !== undefined && possiblyUndefined.length > 0;

Although these days I think I’d probably go for optional chaining (?.) and nullish coalescing (??):

return possiblyUndefined?.length ?? 0 > 0;

Playground link for the above

¹ It’s interesting to see from the link in Leland’s own answer that there’s at least talk of making it a predicate function. But it isn’t one as of this writing. It’s just a conversion function.

Leave a Reply