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:
the compiler does in fact narrow the type of
v
as you pass over thosereturn
statements... it complains, but not because it thinksv
might still be anX
or aY
when 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 returnsundefined
at the end. The only way to fix this is to make sure there's areturn
orthrow
in all code paths.Note again that
v
is first narrowed toY
and then tonever
. One way you can fix the error is to take advantage of the narrowing toY
, and do areturn
there instead of a secondif
test: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 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 | never
instead ofstring | undefined
. Andstring | never
is juststring
. And you can see how it functions as an exhaustiveness check, since it throws an error if you don't narrowv
tonever
:Okay, hope those help. Good luck!