2021-10-09
|~2 min read
|363 words
How do you tell Typescript that you’re covering all of the cases with your switch statement and there’s nothing else that could happen? By using the never
type!
Let’s take a look.
Let’s start by setting up our example. We’ll use a discriminated union of employee types:
enum Role {
Standard = "Standard",
Admin = "Admin",
}
type Standard = {
role: Role.Standard
name: string
age: number
}
type Admin = {
role: Role.Admin
name: string
securityLevel: "Normal" | "Elevated" | "High"
}
type Employee = Admin | Standard
Now that we know what our data looks like, imagine we want to pull out the unique attributes for each of our employees.
We might write a switch statement that looks at the role to determine which type of employee we’re dealing with.
This might look like:
function getUniqueAttribute(employee: Employee) {
switch (employee.role) {
case Role.Standard:
return employee.age
case Role.Admin:
return employee.access
default:
return
}
}
At this point, you may or may not compile depending on your settings. We know that it’s exhaustive because we’re switching on the Role and we have them all.
But, what if a new role were added later? How could we make sure that our function caught it?
We could add an exhaustive check, like so:
function getUniqueAttribute(employee: Employee) {
switch (employee.role) {
case Role.Standard:
return employee.age
case Role.Admin:
return employee.access
default:
const _exhaustiveCheck: never = employee
return _exhaustiveCheck
}
}
This kind of logic, however, is something I found myself writing over and over since I commonly use a switch to discriminate my types.
A small predicate function to handle this might look like:
export function assertUnreachable(_x: never): never {
throw new Error("Didn't expect to get here")
}
Then, we can update the switch statement accordingly:
function getUniqueAttribute(employee: Employee) {
switch (employee.role) {
case Role.Standard:
return employee.age
case Role.Admin:
return employee.access
default:
assertUnreachable(employee)
}
}
In this case we don’t need to return assertUnreachable
because it’ll throw an error - which we can confidently do as our type system will prevent any other type at compilation time.
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!