snippettypescriptCriticalCanonical
How do I check that a switch block is exhaustive in TypeScript?
Viewed 0 times
typescripthowcheckthatexhaustiveblockswitch
Problem
I have some code:
I forgot to handle the
enum Color {
Red,
Green,
Blue
}
function getColorName(c: Color): string {
switch(c) {
case Color.Red:
return 'red';
case Color.Green:
return 'green';
// Forgot about Blue
}
throw new Error('Did not expect to be here');
}I forgot to handle the
Color.Blue case and I'd prefer to have gotten a compile error. How can I structure my code such that TypeScript flags this as an error?Solution
To do this, we'll use the
First step is to write a function:
Then use it in the
At this point, you'll see an error:
The error message indicates the cases you forgot to include in your exhaustive switch! If you left off multiple values, you'd see an error about e.g.
Note that if you're using
You can get a little fancier if you like. If you're using a discriminated union, for example, it can be useful to recover the discriminant property in the assertion function for debugging purposes. It looks like this:
This is a nice pattern because you get compile-time safety for making sure you handled all the cases you expected to. And if you do get a truly out-of-scope property (e.g. some JS caller made up a new
never type (introduced in TypeScript 2.0) which represents values which "shouldn't" occur.First step is to write a function:
function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}Then use it in the
default case (or equivalently, outside the switch):function getColorName(c: Color): string {
switch(c) {
case Color.Red:
return 'red';
case Color.Green:
return 'green';
}
return assertUnreachable(c);
}At this point, you'll see an error:
return assertUnreachable(c);
~~~~~~~~~~~~~~~~~~~~~
Type "Color.Blue" is not assignable to type "never"The error message indicates the cases you forgot to include in your exhaustive switch! If you left off multiple values, you'd see an error about e.g.
Color.Blue | Color.Yellow.Note that if you're using
strictNullChecks, you'll need that return in front of the assertUnreachable call (otherwise it's optional).You can get a little fancier if you like. If you're using a discriminated union, for example, it can be useful to recover the discriminant property in the assertion function for debugging purposes. It looks like this:
// Discriminated union using string literals
interface Dog {
species: "canine";
woof: string;
}
interface Cat {
species: "feline";
meow: string;
}
interface Fish {
species: "pisces";
meow: string;
}
type Pet = Dog | Cat | Fish;
// Externally-visible signature
function throwBadPet(p: never): never;
// Implementation signature
function throwBadPet(p: Pet) {
throw new Error('Unknown pet kind: ' + p.species);
}
function meetPet(p: Pet) {
switch(p.species) {
case "canine":
console.log("Who's a good boy? " + p.woof);
break;
case "feline":
console.log("Pretty kitty: " + p.meow);
break;
default:
// Argument of type 'Fish' not assignable to 'never'
throwBadPet(p);
}
}This is a nice pattern because you get compile-time safety for making sure you handled all the cases you expected to. And if you do get a truly out-of-scope property (e.g. some JS caller made up a new
species), you can throw a useful error message.Code Snippets
function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}function getColorName(c: Color): string {
switch(c) {
case Color.Red:
return 'red';
case Color.Green:
return 'green';
}
return assertUnreachable(c);
}return assertUnreachable(c);
~~~~~~~~~~~~~~~~~~~~~
Type "Color.Blue" is not assignable to type "never"// Discriminated union using string literals
interface Dog {
species: "canine";
woof: string;
}
interface Cat {
species: "feline";
meow: string;
}
interface Fish {
species: "pisces";
meow: string;
}
type Pet = Dog | Cat | Fish;
// Externally-visible signature
function throwBadPet(p: never): never;
// Implementation signature
function throwBadPet(p: Pet) {
throw new Error('Unknown pet kind: ' + p.species);
}
function meetPet(p: Pet) {
switch(p.species) {
case "canine":
console.log("Who's a good boy? " + p.woof);
break;
case "feline":
console.log("Pretty kitty: " + p.meow);
break;
default:
// Argument of type 'Fish' not assignable to 'never'
throwBadPet(p);
}
}Context
Stack Overflow Q#39419170, score: 270
Revisions (0)
No revisions yet.