Typescript narrowing down within union of tuples

Viewed 298

Say I have a function like this:

function foo(n: number): [number, undefined] | [undefined, number] {
  if (n % 2 === 0) {
    return [n, undefined];
  }
  return [undefined, n];
}

And then calling this function like this:

function bar() {
  const [a, b] = foo(2);
  if (a === undefined) {
    return b; // TypeScript infers b as number | undefined
  }
  return a; // TypeScript infers a as number (good!)
}

Because function foo returns either tuple (number, undefined) or (undefined, number), after not passing the a === undefined check, TypeScript is able to infer that the type of a is number when returning it.

And within the if block, because a is undefined we (humans) can infer the return type of foo here as [undefined, number] hence, type of b as number. However TypeScript doesn't seem intelligent enough to be able to tell this and reports that it's number | undefined.

Is there a workaround for narrowing down on union of tuples?

2 Answers

Update: The improved control-flow analysis in TypeScript 4.6 resolves the issue, so the fix below is no longer necessary. (See joematune's answer to this question.)

TypeScript's discriminating unions don't keep track of the dependency between a and b, so after you destructure the array, narrowing a won't affect the type of b. To still achieve the desired narrowing, you can directly test the array element, like this:

function bar() {
  const arr = foo(2);
  if (arr[0] === undefined) {
    const b = arr[1]
    return b; // TypeScript infers b as number (good!)
  }
  const a = arr[0]
  return a; // TypeScript infers a as number (good!)
}

TypeScript playground

As of ver. 4.6, TypeScript now handles Destructured Discriminated Unions and your code snippet will no longer error.

TypeScript playground

function bar() {
  const [a, b] = foo(2);
  if (a === undefined) {
    return b; // TypeScript infers b as number (good!)
  }
  return a; // TypeScript infers a as number (good!)
}

TypeScript 4.6 RC - [link]

Related