Regrettably, TypeScript lacks a certain functionality that has been requested and documented at microsoft/TypeScript#21985.
Even though the compiler can narrow the type of data
to the impossible never
type towards the end of the function, it fails to recognize that this renders the bottom section of the function unreachable. Consequently, the compiler does not grasp the concept of an "exhaustive" range of if
/else
statements. Until microsoft/TypeScript#21985 is implemented, a workaround will be necessary.
The compiler does acknowledge the existence of an exhaustive switch
statement. Hence, one alternative for cases like this is to restructure the code to utilize switch
instead. As applying this method to decodeData()
isn't straightforward, we will move on from that option.
Another strategy involves introducing an assertion function known as assertNever()
. This function only receives inputs that have been narrowed down to never
, and always triggers an unequivocal throw
, signaling to the compiler that any subsequent code is unreachable:
function assertNever(x: never): never {
throw new Error("Unexpected Value: " + x);
}
Subsequently, after completing the logic in decodeData()
, you would invoke assertNever(data)
:
function decodeData(
data: string | number[] | ArrayBuffer | Uint8Array,
): string {
const td = new TextDecoder();
if (typeof (data) === "string") {
return data;
} else if (data instanceof Array) {
return td.decode(new Uint8Array(data));
} else if (data instanceof ArrayBuffer) {
return td.decode(data);
} else if (data instanceof Uint8Array) {
return td.decode(data);
}
assertNever(data);
}
This approach eliminates the "not all paths return a value" error since assertNever()
always throws, while also ensuring exhaustiveness - without which assertNever(data)
wouldn't compile successfully:
function badDecodeData(
data: string | number[] | ArrayBuffer | Uint8Array,
): string {
const td = new TextDecoder();
if (typeof (data) === "string") {
return data;
} else if (data instanceof Array) {
return td.decode(new Uint8Array(data));
//} else if (data instanceof ArrayBuffer) {
// return td.decode(data);
} else if (data instanceof Uint8Array) {
return td.decode(data);
}
assertNever(data); // error!
// -------> ~~~~
// Argument of type 'ArrayBuffer' is not assignable to parameter of type 'never'
}
In the example provided, I have commented out one of the cases and as expected, assertNever(data)
raises an issue stating that ArrayBuffer
(the omitted case) cannot be considered a valid never
.
For those interested, here is the Playground link to code