Posted on 2020-02-04
When working with union types it is very common that you add additional types while your software evolves.
Let's take this union type:
interface AnswerYes {
result: "yes";
}
interface AnswerNo {
result: "no";
}
type Answer = AnswerYes | AnswerNo;
and do a switch
on that:
export function printToConsole(answer: Answer): void {
switch (answer.result) {
case "yes":
console.log("YES");
break;
case "no":
console.log("NO");
break;
}
}
If you add another type to your union the code above will fail to handle that case and the compiler won't help you.
interface AnswerMaybe {
result: "maybe";
}
type Answer = AnswerYes | AnswerNo | AnswerMaybe;
To fix this issue we can use the following utility function:
export function assertUnreachable(x: never): never {
throw new Error("assertUnreachable: " + x);
}
This function takes a parameter of type never
as its argument, so you can only call it if typescript infers that type.
If you use that as the default
case in your switch statement like this
export function printToConsole(answer: Answer): void {
switch (answer.result) {
case "yes":
console.log("YES");
break;
case "no":
console.log("NO");
break;
default:
assertUnreachable(answer); // Compiler error
}
}
you'll get an error after adding the AnswerMaybe
to the union type, so the compiler will tell you exactly which
cases you'll still need to handle.