Exhaustiveness checking
We would like the compiler to tell us when we don’t cover all variants of the discriminated union. For example, if we add Triangle
to Shape
, we need to update area
as well:
1 2 3 4 5 6 7 8 9 | type Shape = Square | Rectangle | Circle | Triangle; function area(s: Shape) { switch (s.kind) { case "square" : return s.size * s.size; case "rectangle" : return s.height * s.width; case "circle" : return Math.PI * s.radius ** 2; } // should error here - we didn't handle case "triangle" } |
There are two ways to do this. The first is to turn on --strictNullChecks
and specify a return type:
1 2 3 4 5 6 7 | function area(s: Shape): number { // error: returns number | undefined switch (s.kind) { case "square" : return s.size * s.size; case "rectangle" : return s.height * s.width; case "circle" : return Math.PI * s.radius ** 2; } } |
Because the switch
is no longer exhaustive, TypeScript is aware that the function could sometimes return undefined
. If you have an explicit return type number
, then you will get an error that the return type is actually number | undefined
. However, this method is quite subtle and, besides, --strictNullChecks
does not always work with old code.
The second method uses the never
type that the compiler uses to check for exhaustiveness:
1 2 3 4 5 6 7 8 9 10 11 | function assertNever(x: never): never { throw new Error( "Unexpected object: " + x); } function area(s: Shape) { switch (s.kind) { case "square" : return s.size * s.size; case "rectangle" : return s.height * s.width; case "circle" : return Math.PI * s.radius ** 2; default : return assertNever(s); // error here if there are missing cases } } |
Here, assertNever
checks that s
is of type never
— the type that’s left after all other cases have been removed. If you forget a case, then s
will have a real type and you will get a type error. This method requires you to define an extra function, but it’s much more obvious when you forget it.
Please login to continue.