When using switch statements with Union Types in TypeScript, it is important to ensure that all cases are exhaustively covered. If a new type is added to the union type but the switch statement is not updated, it can lead to unexpected bugs.
A technique for performing this exhaustiveness check at compile time is to assign the never type in the default clause of the switch statement.
What Is the never Type?
The never type is a special type in TypeScript that represents “a value that never occurs.” It is used for the type of unreachable code paths, such as when a function never returns (enters an infinite loop, always throws an exception, etc.).
More details: TypeScript Deep Dive: never
In simple terms, the never type represents something that is “impossible.”
How Exhaustiveness Checking with never Works
Here is a sample implementation of this technique:
type Sample = "A" | "B"; // Define a union type
function testFunc(sample: Sample): void {
switch (sample) {
case "A":
console.log("Case A:", sample);
break;
case "B":
console.log("Case B:", sample);
break;
default:
// Exhaustiveness check here
// If 'sample' is not of type 'never', a compile error occurs
const _: never = sample;
// If there is any possibility of reaching here, TypeScript reports an error
}
}
// Usage examples
testFunc("A"); // "Case A: A"
testFunc("B"); // "Case B: B"
// testFunc("C"); // Compile error: Argument of type '"C"' is not assignable to parameter of type '"A" | "B"'.
Explanation
type Sample = "A" | "B";defines a union typeSamplethat only allows the string literals"A"or"B".- The
switch (sample)statement branches processing based on the value ofsample. - In the
defaultclause, after bothcase "A"andcase "B"have been handled, the type ofsampleshould theoretically be in an “impossible” state. Ifsampleis a value that is neither"A"nor"B", it would reach thisdefaultclause. - The line
const _: never = sample;is the key. TypeScript’s type checker expects the type ofsampleto beneverat this point.- If the
caseclauses in theswitchstatement cover all patterns of the union type, there is no value ofsamplethat can reach thedefaultclause, sosampleis inferred as typenever, and compilation succeeds. - For example, if
| "C"is added to the union typeSamplebutcase "C":is forgotten, the type ofsamplereaching thedefaultclause is inferred as"C", notnever. Attempting to assign a value of type"C"to a variable of typenevercauses TypeScript to report a compile error.
- If the
By using this technique, you can guarantee at compile time that all patterns of a Union type have been handled. This ensures that when a new type is added to the Union type in the future, the absence of corresponding handling in the switch statement is detected early as an error.
References
- Software Design Editorial Department, “Software Design May 2024 Issue,” Gijutsu-Hyoron (pp.102-123)
- TypeScript Deep Dive Japanese Version: never