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
ifstatement 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:
the compiler does in fact narrow the type of
vas you pass over thosereturnstatements... it complains, but not because it thinksvmight still be anXor aYwhen you fall off the bottom of the function. It complains because it analyzes code paths in isolation from the narrowing ofv, so it sees a code path which implicitly returnsundefinedat the end. The only way to fix this is to make sure there's areturnorthrowin all code paths.Note again that
vis first narrowed toYand then tonever. One way you can fix the error is to take advantage of the narrowing toY, and do areturnthere instead of a secondiftest:Now there's no error. If you trust your
instanceof Xcheck, this is probably the way to go. The other standard way to go here is to use an exhaustiveness checking helper functionassertNever():The
assertNever()function throws an error if you call it, so it returnsnever. But it requires its parameter to be of typenever, so calling it should be a compiler error unless the code is unreachable:Now
what()is known to returnstring | neverinstead ofstring | undefined. Andstring | neveris juststring. And you can see how it functions as an exhaustiveness check, since it throws an error if you don't narrowvtonever:Okay, hope those help. Good luck!