简体   繁体   中英

Typescript possible code paths vs. typing loophole?

Given the code

class X {a:string = "a"}
class Y {b:number = 1}

// does not compile
function what(v: X|Y) {
  if (v instanceof X) {
    return "its an X";
  }
  if (v instanceof Y) {
    return "its a Y";
  }
  //missing return
}

the typescript compiler says:

error TS7030: Not all code paths return a value.

and rightly so, since due to structural typing I could call what as

let v = {b:1}
what(v)

since then v is structurally compatible with Y . Now I change what to be

function what(v: X|Y) {
  if ("a" in v) {
    return "its an X";
  }
  if ("b" in v) {
    return "its a Y";
  }
  // missing return
}

and still get the compile error.

I wonder whether the compiler just cannot derive that one of the if branches will be taken, or whether there is a loophole that still allows me to pass in a compatible object that does not match any of the two if branches.

This is a design limitation in TypeScript. You, reasonably, expect that exhaustiveness checking will make the compiler realize that the code after the last if statement is unreachable. But this doesn't happen, and it doesn't look like they are planning to address that anytime soon.

Let's see what we can do about it ourselves. In the following code:

class X { a: string = "a" }
class Y { b: number = 1 }

// does not compile
function what(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  v // Y
  if (v instanceof Y) {
    return "its a Y";
  }
  v // never
}

the compiler does in fact narrow the type of v as you pass over those return statements... it complains, but not because it thinks v might still be an X or a Y when you fall off the bottom of the function. It complains because it analyzes code paths in isolation from the narrowing of v , so it sees a code path which implicitly returns undefined at the end. The only way to fix this is to make sure there's a return or throw in all code paths.

Note again that v is first narrowed to Y and then to never . One way you can fix the error is to take advantage of the narrowing to Y , and do a return there instead of a second if test:

function what(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  v // Y
  return "its a Y";
}

Now there's no error. If you trust your instanceof X check, this is probably the way to go. The other standard way to go here is to use an exhaustiveness checking helper function assertNever() :

function assertNever(x: never): never {
  throw new Error("OH NOES!!");
}

The assertNever() function throws an error if you call it, so it returns never . But it requires its parameter to be of type never , so calling it should be a compiler error unless the code is unreachable:

function what(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  v // Y
  if (v instanceof Y) {
    return "its a Y";
  }
  v // never
  return assertNever(v);
}

Now what() is known to return string | never string | never instead of string | undefined string | undefined . And string | never string | never is just string . And you can see how it functions as an exhaustiveness check, since it throws an error if you don't narrow v to never :

function oops(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  return assertNever(v); // error, v is Y, not never
}

Okay, hope those help. Good luck!

I know a different version working with interfaces and tagged union. And this version compile correctly with all code path returning a value.

interface IX {kind: "a"; a: string }
interface IY {kind: "b"; b: number }

function what2(v: IX|IY): string {
    switch (v.kind) { 
        case "a":
         return "its an X";
         case "b":
         return "its an Y";
    }
}

var r1 = what2({kind: "a", a: "hello"});
var r2 = what2({kind: "b", b: 33});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM