Search code examples
typescripttypeguardstype-narrowingtypescript4.0compound-operator

How to preserve type guard narrowing when using logical assignment?


The following is an example that reproduces the problem:

type F = () => number;

type R = {
  [ x: string ]: R | F | undefined
}

const isFunc = <T extends (...args: any[]) => any>(maybe:unknown) : maybe is T => typeof maybe === "function";

const check = (i: R) => {

  let tmp:R = i;

  let curr = tmp["something"];

  if( isFunc<F>(curr) ) return;

  curr // R | undefined, expected

  tmp = curr || (curr = {}); //ok, expected

  tmp = curr ||= {}; //Index signature is missing in type 'F'

};

As you can see, after the type guard, curr is correctly narrowed to R | undefined. After that, I am reassigning tmp to curr and defaulting the latter to an empty object should it be missing.

Now, if the A || A = B approach is used, curr on the left side of the logical OR is properly narrowed to R | undefined. However, if I use the logical OR assignment to make the intent clearer, curr is inferred as R | F | undefined. This obviously results in an error, since F is not assignable to R.

The question is - what would be the reason for curr to be losing the narrowing in the second case?

Playground


Solution

  • After submitting this as an issue in the source repository, the behavior has been confirmed to be a bug (more like a design limitation because no control-flow analysis is done when using logical assignment operators as opposed to short-circuiting assignment with logical operators).