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
- Assigning the remainder to
neverfails when a case is added. - A helper makes the error message readable.
- A Record over the union is the table-driven alternative.
Keywords and builtins used here
EventJSONObjectRecordcaseconstfunctionnevernumberreturnstringswitchthrowtype
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.
Step 2 of 3 in Exhaustiveness & tests, step 10 of 12 in Type-level programming.