Skip to main content

Exhaustive checking with empty type

flow.org/try

type Cases = 'A' | 'B';

function exhaust(x: Cases) {
if (x === 'A') {
// do stuff
} else if (x === 'B') {
// do different stuff
} else {
// only true if we handled all cases
(x: empty);
}
}

What happens when you add new case 'C'? You will get this error:

Cannot cast x to empty because string literal C [1] is incompatible with empty [2].

[1] 5│ function exhaust(x: Cases) {
6│ if (x === 'A') {
7│ // do stuff
8│ } else if (x === 'B') {
9│ // do different stuff
10│ } else {
11│ // only true if we handled all cases
[2] 12│ (x: empty);
13│ }
14│ }
15│

You can be sure that you covered all the cases this way. Another real-life example:

export function exhaustB(reason: 'magicLink' | 'signUpConfirmation' | 'resetPassword') {
switch (reason) {
case 'magicLink':
return __('account.check_email_magic_link');
case 'signUpConfirmation':
return __('account.check_email_sign_up');
case 'resetPassword':
return __('account.you_will_recieve_password');
default:
return invariant(false, 'Unsupported reason: %j', (reason: empty));
}
}

Notice how is the empty type used at the same time with reason.