I want to build something like this:
type MyValues = "good" | "bad" | "ugly"
function doSomething(values:MyValues) {
return startBuilder(values)
.case("good", 1)
.case("bad", 2)
.exhaustive() // this should give an compiler error, since I forgot the `ugly` }
I tried to figure out the tail
using TypeScript's infer
- but with no luck:
type Extract<T, A> = A extends T
? T extends infer Tail
? Tail
: never
: never;
type X = Extract<Values, 'good'>;
But this does not work. Any ideas?
Here is a playground: https://stackblitz.com/edit/typescript-mguypt
CodePudding user response:
We'll first need a type that checks if two types are EXACTLY equal, a simple extends
won't do the trick here:
type Equals<A, B> = (<T>() => T extends A ? true : false) extends (<T>() => T extends B ? true : false) ? true : false;
The explanation behind why this interesting-looking type works can be found here by jcalz no less.
Then we define the Builder class:
class Builder<T, SoFar extends ReadonlyArray<unknown> = []> {
private cases = new Map<T, unknown>();
}
It takes a generic T
which is what it will check its cases against, and a generic not intended to be passed by the end user. This generic SoFar
stores which cases have been used so far.
So let's build the case
function now:
case<V extends T>(v: V, d: unknown): Builder<T, [...SoFar, V]> {
this.cases.set(v, d);
return this as Builder<T, [...SoFar, V]>;
}
It takes something that extends T
and any value, then returns the builder as Builder<T, [...SoFar, V]>
, which means we add the case to the cases used up so far.
Finally in here:
exhaustive(...args: Equals<T, SoFar[number]> extends true ? [] : [{ error: "Builder is not exhaustive." }]) {
}
We check if T
and SoFar[number]
are exactly the same. If they are, then we have exhausted all cases. Otherwise, we say that we expect a parameter.
Because you will likely call exhaustive
with no parameters, calling it without using up all cases will display a nice error message for the user.
You can even go one step further and include all the missing cases in the error message! The second playground link demonstrates this.