assertNever

Naseebullah Ahmadi  Senior Software Engineer, London

A one-line helper that makes a `switch` over a discriminated union a compile error the moment a case is added and left unhandled, with a runtime throw as the safety net for data that violated the types at the edges.

2 min read
#tooling

In the default branch of a switch over a discriminated union, #typescript narrows the value down to never - but only once every other case is handled. Pass it to a function typed (x: never) and that guarantee becomes load-bearing: add a variant, miss a case, and the code stops compiling.

@itsnas TypeScript
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; side: number }
  | { kind: 'rectangle'; width: number; height: number }
 
function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.side ** 2
    case 'rectangle':
      return shape.width * shape.height
    default:
      // Add a 4th Shape or drop a case above and this line errors:
      // Argument of type '{ kind: "..." }' is not assignable to
      // parameter of type 'never'.
      return assertNever(shape)
  }
}
 
function assertNever(value: never): never {
  // Only reached if a value slipped past the type system: an API
  // response, persisted state, a JSON.parse. Fail loudly.
  throw new Error(`Unhandled kind: ${JSON.stringify(value)}`)
}
main
Nas (@itsnas)