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:
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:
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:
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.