When using type narrowing with TypeScript, never
functions do not react the same way every time.
const getValue = () => {
const value = process.env.VARIABLE
if (!value) process.exit(1)
return value
}
In the example below, getValue
inferred type is () => string
, because value
is typed string | undefined
, and TypeScript is able to identify that the remaining code after process.exit
is unreachable, and the return value is always a string.
const exit = () => {
return process.exit(1)
}
const getValue = () => {
const value = process.env.VARIABLE
if (!value) exit()
return value
}
In this second example, exit
is typed () => never
, and getValue
inferred type is () => string | undefined
, because TypeScript is unable to detect that the rest of the function is unreachable if value
does not exists. However, if I add a return
before the exit
call (so return exit()
), it works as desired.
Why does it works like this? Is there a way to make it work in the second case?
TypeScript's support for functions that return never
, as implemented in microsoft/TypeScript#32695, only works if the function type is explicitly annotated to return never
. It would be nice if the type checker could just infer that, but allowing that would make control flow analysis much harder to implement so that it performs well. As it says in the above-liked PR, "this particular rule exists so that control flow analysis of potential assertion calls doesn't circularly trigger further analysis".
So, to get the behavior you're looking for, you should annotate exit
as being of type () => never
, like this:
const exit: () => never = () => {
return process.exit(1)
}
and then it just works:
const getValue = () => {
const value = process.env.VARIABLE
if (!value) exit()
return value
}
// const getValue: () => string