I have an enum of operations (that I can't change, unfortunately):
enum OpType {
OpA = 0,
OpB = 1,
}
…and a set of types for objects that carry the data needed for each operation:
type A = {
readonly opType: OpType.OpA;
readonly foo: number;
};
type B = {
readonly opType: OpType.OpB;
readonly bar: string;
};
…and finally a handler function that ensure that each operation is handled:
type Ops = A | B;
export const ensureExhaustive = (_param: never) => {};
export const handleOp = (op: Ops) => {
switch (op.opType) {
case OpType.OpA:
if (op.foo < 80) { /* … */ }
break;
case OpType.OpB:
if (op.bar === 'foo') { /* … */ }
break;
default:
ensureExhaustive(op);
}
}
However, this handleOp function only really assures that we handle what was explicitly added to the Ops union – the connection to the OpType enum has been lost, so if a OpC = 2 is added to the enum, it won't be detected that this isn't handled.
How can I “connect” the enum values (probably through an Ops type) to the switch statement in handleOp to ensure that each value is handled?
You could just recreate the
ensureExhaustivefunction as a type:Then you could define a dummy type that just makes sure something is
never:Conveniently, there exists a utility
Excludethat already has the behavior we want:In other words, the result of this type is
neverif the second argument encompasses the first. Translating to our use case, we want this to beneverifOps["opType"]uses all members ofOpType.Another trick we can do is to make
ensureExhaustivegeneric, so that it would have the signatureso we can then utilize instantiation expressions that were introduced in 4.7, removing the need for an
EnsureExhaustivetype:Of course, if you have
noUnusedLocalsenabled or a linter, this dummy type might cause an error or warning.