typestar

Exhaustiveness in TypeScript

The compiler proves you handled every case, if you let it.

type Event =
  | { kind: "start"; lang: string }
  | { kind: "finish"; tpm: number }
  | { kind: "abort"; reason: string };

function assertNever(value: never): never {
  throw new Error(`unhandled: ${JSON.stringify(value)}`);
}

function render(event: Event): string {
  switch (event.kind) {
    case "start":
      return `starting ${event.lang}`;
    case "finish":
      return `finished at ${event.tpm}`;
    case "abort":
      return `aborted: ${event.reason}`;
    default:
      return assertNever(event);
  }
}

const LABELS: Record<Event["kind"], string> = {
  start: "start",
  finish: "finish",
  abort: "abort",
};

console.log(render({ kind: "finish", tpm: 98 }), Object.keys(LABELS).length);

How it works

  1. Assigning the remainder to never fails when a case is added.
  2. A helper makes the error message readable.
  3. A Record over the union is the table-driven alternative.

Keywords and builtins used here

The run, in numbers

Lines
29
Characters to type
650
Tokens
183
Three-star pace
110 tpm

At the three-star pace of 110 tokens a minute, this run takes about 100 seconds.

Type this snippet

Step 2 of 3 in Exhaustiveness & tests, step 10 of 12 in Type-level programming.

← Previous Next →