Distributive conditionals in TypeScript
A conditional over a naked type parameter runs once per union member.
type Boxed<T> = T extends unknown ? { value: T } : never;
type NotBoxed<T> = [T] extends [unknown] ? { value: T } : never;
type Distributed = Boxed<string | number>;
type Together = NotBoxed<string | number>;
type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;
type MyNonNullable<T> = T extends null | undefined ? never : T;
type Levels = "debug" | "info" | "warn" | "error";
type Loud = MyExclude<Levels, "debug" | "info">;
type Quiet = MyExtract<Levels, "debug" | "info">;
const one: Distributed = { value: 1 };
const both: Together = { value: "either" };
const loud: Loud = "error";
const quiet: Quiet = "debug";
const defined: MyNonNullable<string | null> = "here";
console.log(one, both, loud, quiet, defined);
How it works
- Distribution is why Exclude and Extract work.
- Wrapping the parameter in a tuple switches distribution off.
- It is the single most surprising rule in the type system.
Keywords and builtins used here
DistributedLoudMyNonNullableQuietTTogetherconstextendsnevernumberstringtype
The run, in numbers
- Lines
- 20
- Characters to type
- 763
- Tokens
- 206
- Three-star pace
- 105 tpm
At the three-star pace of 105 tokens a minute, this run takes about 118 seconds.
Step 3 of 3 in Conditional types, step 3 of 12 in Type-level programming.