How to prevent a union of tuples turning into a tuple of unions?

Trying to write a function that returns gracefully when an unexpected "failure" occurs. Was thinking of gonig with a go-style function return on this one, and typing the return as [Val, null] | [null, Error].

However, when trying to type-guard the return value with an if statement,

const [val, err] = myFunc(); // => [Val, null] | [null, Error]

if (err) {
  handle(err);
  return;
}

// At this point in code, return value has to be of type [Val, null]

doSomethingWith(val) // Error! Type 'null' is not assignable to type 'Val'

Seems confusing given a similar approach with objects works just fine, (Link to playground)

const res = myFunc(); // => {type: 'Error'} | {type: 'Success', data: Val}


if (res.type === 'Error') {
  handle(res);
  return;
}

// In this example, res is correctly narrowed to {type: 'Success', data: Val}

doSomethingWith(res.data)

Seems the union of tuples is turned into a tuple of unions,

from [Val, null] | [null, Error]

to [Val | null, null | Error]

Is this expected behaviour? What's the reason for this, and is there a way around it?


It didn't turn into a tuple of unions - it turned into two variables with union types, because you split the result into two variables. Typescript does not track dependencies between the types of different variables, so your type guard on the variable err does not and cannot narrow the type of the variable val.

Solution: assign the result of your function to a single variable without destructuring, then use result[0] and result[1] to refer to its components.


You code now works fine on TypeScript 4.6 beta.

Demo on playground

Info about "Control Flow Analysis for Dependent Parameters" in TS 4.6 beta announcement