一键导入
exhaustiveness-checking
Use when handling tagged unions. Use when adding new cases to discriminated unions. Use when switch statements must cover all cases.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when handling tagged unions. Use when adding new cases to discriminated unions. Use when switch statements must cover all cases.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when defining global types. Use when augmenting window. Use when typing environment variables. Use when working with build-time constants. Use when configuring type definitions.
Use when migrating JavaScript to TypeScript. Use when gradually adopting TypeScript. Use when working with mixed codebases. Use when converting large projects. Use when teams are learning TypeScript.
Use when writing asynchronous code. Use when tempted to use callbacks. Use when composing multiple async operations.
Use when creating types from example data. Use when types don't match all cases. Use when API responses vary.
Use when writing type annotations on variables. Use when TypeScript can infer the type. Use when code feels cluttered with types.
Use when defining array-like types. Use when tempted to use number as index type. Use when understanding array keys.
| name | exhaustiveness-checking |
| description | Use when handling tagged unions. Use when adding new cases to discriminated unions. Use when switch statements must cover all cases. |
Use never to ensure all cases in a union are handled.
When you add a new variant to a union type, TypeScript can automatically flag every switch statement that needs updating. This catches errors of omission at compile time.
ALWAYS add exhaustiveness checking to switch statements on union types.
Remember:
nevernever except neverWithout exhaustiveness checking, new union variants are silently ignored:
type Shape = Box | Circle | Line;
function drawShape(shape: Shape, ctx: CanvasRenderingContext2D) {
switch (shape.type) {
case 'box':
ctx.rect(...shape.topLeft, ...shape.size);
break;
case 'circle':
ctx.arc(...shape.center, shape.radius, 0, 2 * Math.PI);
break;
// Forgot 'line' - NO ERROR! Lines silently don't draw.
}
}
assertUnreachable Helperfunction assertUnreachable(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
function drawShape(shape: Shape, ctx: CanvasRenderingContext2D) {
switch (shape.type) {
case 'box':
ctx.rect(...shape.topLeft, ...shape.size);
break;
case 'circle':
ctx.arc(...shape.center, shape.radius, 0, 2 * Math.PI);
break;
default:
assertUnreachable(shape);
// If we missed a case, shape won't be 'never' and we get a type error!
}
}
// Add a new shape type
interface Line {
type: 'line';
start: Coord;
end: Coord;
}
type Shape = Box | Circle | Line; // Added Line
// Now drawShape shows an error:
function drawShape(shape: Shape, ctx: CanvasRenderingContext2D) {
switch (shape.type) {
case 'box': /* ... */ break;
case 'circle': /* ... */ break;
default:
assertUnreachable(shape);
// ~~~~~
// Argument of type 'Line' is not assignable to parameter of type 'never'
}
}
Fix by handling the new case:
function drawShape(shape: Shape, ctx: CanvasRenderingContext2D) {
switch (shape.type) {
case 'box':
ctx.rect(...shape.topLeft, ...shape.size);
break;
case 'circle':
ctx.arc(...shape.center, shape.radius, 0, 2 * Math.PI);
break;
case 'line':
ctx.moveTo(...shape.start);
ctx.lineTo(...shape.end);
break;
default:
assertUnreachable(shape); // Now shape is 'never', no error!
}
}
After handling all cases, the remaining type is never:
function processShape(shape: Shape) {
switch (shape.type) {
case 'box': break;
case 'circle': break;
case 'line': break;
default:
shape
// ^? (parameter) shape: never
}
}
If you miss a case, the type isn't never:
function processShape(shape: Shape) {
switch (shape.type) {
case 'box': break;
case 'circle': break;
// (forgot 'line')
default:
shape
// ^? (parameter) shape: Line
}
}
Since Line is not assignable to never, you get a type error.
You can also use return types to enforce exhaustiveness:
function getShapeName(shape: Shape): string {
switch (shape.type) {
case 'box':
return 'Box';
case 'circle':
return 'Circle';
// Missing 'line' - TypeScript error!
// Function lacks ending return statement and return type does not include 'undefined'
}
}
This only works if:
strictNullChecks is enabled// Types
type Coord = [x: number, y: number];
interface Box {
type: 'box';
topLeft: Coord;
size: Coord;
}
interface Circle {
type: 'circle';
center: Coord;
radius: number;
}
interface Line {
type: 'line';
start: Coord;
end: Coord;
}
type Shape = Box | Circle | Line;
// Helper
function assertUnreachable(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
// Usage - guaranteed to handle all shapes
function getArea(shape: Shape): number {
switch (shape.type) {
case 'box':
return shape.size[0] * shape.size[1];
case 'circle':
return Math.PI * shape.radius ** 2;
case 'line':
return 0; // Lines have no area
default:
return assertUnreachable(shape);
}
}
Sometimes you intentionally want to ignore some cases:
function handleCommonShapes(shape: Shape) {
switch (shape.type) {
case 'box':
case 'circle':
// Handle common cases
break;
// Intentionally ignore 'line' - don't add assertUnreachable here
}
}
Pressure: "We have a default case, so it's fine"
Response: A silent default hides bugs when new variants are added.
Action: Use assertUnreachable in default to make missing cases explicit.
Pressure: "We know where to add new cases"
Response: Human memory fails. Compiler checking doesn't.
Action: Let TypeScript track it for you.
| Excuse | Reality |
|---|---|
| "We'll remember to update" | You won't, or your teammates won't |
| "Default handles unknowns" | It hides bugs from new variants |
| "It's just one switch" | Union types often have many switches |
// The pattern
function assertUnreachable(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
// In switch statements
switch (union.type) {
case 'a': /* ... */ break;
case 'b': /* ... */ break;
default:
assertUnreachable(union); // Type error if cases missing
}
Turn missing cases into compile-time errors with never.
When handling tagged unions, add assertUnreachable(value) to your default case. This ensures that adding new variants to the union produces type errors everywhere the union is handled, catching errors of omission at compile time rather than runtime.
Based on "Effective TypeScript" by Dan Vanderkam, Item 59: Use Never Types to Perform Exhaustiveness Checking.