Use TypeScript’s never Type for Exhaustiveness Checking

Marius Schulz
InstructorMarius Schulz

Share this video with your friends

Send Tweet
Published 6 years ago
Updated 4 years ago

TypeScript 2.0 introduced a new primitive type called never, the type of values that never occur. It helps model the completion behavior of functions more accurately and can also be used for exhaustiveness checking.

Here I have prepared a function that contains a single while loop, which in turn contains four console.log statements. Notice that this is an endless loop, the condition is always true, and we also never leave the loop body using break, return, or throw. This means that the end of the function is effectively unreachable.

If I hover over the sink identifier, I can see that the inferred return type of this function is never. Therefore, if I call sink and assign the returned value to a variable the type of that variable is inferred to be never as well.

The idea is that this part of the program is unreachable since the sink function does not ever return. I also can't really do anything with the result variable. If I type result. I don't get any auto completion here. Again, this part of the program is unreachable so the type result is never.

If I had used an error function instead of a function expression the type checker would have inferred the same return type never. However, if I had used a function declaration like this the return type would be different. In this case, it would be void. This is the case because of various compatibility reasons.

I want to point out that there is a conceptual difference between void and never. A function that doesn't return anything is different from a function that never returns at all. For example, this greet function has an inferred return type of void, because it implicitly returns undefined.

On the other hand, this fail function always throws, so it never completes normally. This is why its return type is inferred to be never. To ensure that the sink function never returns we can add an explicit return type annotation.

This way if there is a way to leave the function, for example, because we break out of the loop, the type checker tells us that a function returning never cannot have a reachable endpoint. You will also encounter the never type in conjunction with control flow analysis and type guards.

Let's take our trim and lower function again. First, we check if text is a string, and if it is, we return a trimmed and lower-cased version of that text. Afterwards we check whether text is null, and if so, we return null directly. In all other cases, we return text unchanged.

Let's see the control flow based type analysis in action. Within the first type guard the type of text has been narrowed to string, because that is exactly what we checked right here. If we make it past the first if statement the only remaining candidate type for text is null, because otherwise we would have already left the function.

If text is null, we leave the function here as well. That is why in line 10 text is typed to be never. We've already tried string and null and we have no candidates left.

Lastly, I want to show you how to use the never type for exhaustiveness checking. Here I have to find an enum called shirt size with three cases, S, M, and L. Down below I have a pretty print function that accepts a size and returns a full description for the abbreviation.

What happens if in the future I decide to add another shirt size like XL? We don't get a type error, but now our pretty print function is no longer exhaustive. It would be great if the compiler could force us to deal with every single case so we don't forget one by accident.

We can achieve this by adding a default case and calling into the assert never function, which we're going to define in just a second. Assert never is a function that accepts a single value of type never and which has a return type of never.

It unconditionally throws an error saying that we have an unexpected value and it also includes that value as part of the error message. The idea is that we can only pass size to the assert never function if size is of type never, which it is not right here because it could still be the XL case.

If we also handle the XL case, for example by returning the string extra-large, the type error goes away. Size is now of type never down here and the type checker is happy. The benefit of this solution is that we cannot forget to deal with any case.

If I now go ahead and add yet another case to the enum, such as XS, the type error in line 19 comes back saying that the type shirt size XS is not assignable to the type never.

Brendan Whiting
Brendan Whiting
~ 5 years ago

You rick rolled us...