Skip to content

Instantly share code, notes, and snippets.

@gabrielelana
Last active January 14, 2022 18:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gabrielelana/bce0c153e1b1f10fc154ad5fa053715b to your computer and use it in GitHub Desktop.
Save gabrielelana/bce0c153e1b1f10fc154ad5fa053715b to your computer and use it in GitHub Desktop.
From Union Type to Union Type of all Combinations without Repetition in TypeScript
type Tuple<X, Y> = [X, Y];
type CompatibleWith<X, Y> = X extends Y ? Tuple<X, true> : Tuple<X, false>;
type Equals<X, Y> = [X] extends [Y] ? [Y] extends [X] ? Tuple<Y, true> : Tuple<Y, false> : Tuple<Y, false>
type Assert<X extends Tuple<any, true>> = X extends Tuple<infer Y, true> ? Y : never;
const assertUnreacheable = (x: never): never => { throw new Error() };
type ReadonlyAppendToAll<X, L extends ReadonlyArray<ReadonlyArray<any>>> =
L extends readonly []
? readonly [readonly [X]]
: L extends readonly [infer H, ...infer T]
? H extends ReadonlyArray<any>
? T extends ReadonlyArray<ReadonlyArray<any>>
? ReadonlyAppendToAll<X, T> extends ReadonlyArray<ReadonlyArray<any>>
? readonly [readonly [X, ...H], ...ReadonlyAppendToAll<X, T>]
: never
: never
: never
: never
type _000001 = Assert<Equals<
readonly [readonly ["3"]],
ReadonlyAppendToAll<"3", []>
>>
type _000002 = Assert<Equals<
readonly [readonly ["2", "3"], readonly ["2"]],
ReadonlyAppendToAll<"2", ReadonlyAppendToAll<"3", []>>
>>
type _000003 = Assert<Equals<
readonly [readonly ["1", "2", "3"], readonly ["1", "2"], readonly ["1"]],
ReadonlyAppendToAll<"1", ReadonlyAppendToAll<"2", ReadonlyAppendToAll<"3", []>>>
>>
// We want to have a type that can be a combination of certain values without repetition
// We start from the values in an array
type ActionNames = ["Quote", "Save", "Emit"]
// We get the union type of those values
type ActionName = ActionNames[number]
type ReadonlyCombinationsOf_<L extends Array<any>> =
L extends []
? []
: L extends [infer H, ...infer T]
? [...ReadonlyAppendToAll<H, ReadonlyCombinationsOf_<T>>, ...ReadonlyCombinationsOf_<T>]
: never
type ReadonlyCombinationsOf<L extends Array<any>> = ReadonlyCombinationsOf_<L> extends Array<any> ? readonly [readonly [], ...ReadonlyCombinationsOf_<L>] : never
type _000004 = Assert<Equals<
readonly [readonly [], readonly ["Quote", "Save", "Emit"], readonly ["Quote", "Save"], readonly ["Quote", "Emit"], readonly ["Quote"], readonly ["Save", "Emit"], readonly ["Save"], readonly ["Emit"]],
ReadonlyCombinationsOf<ActionNames>
>>
type _000005 = Assert<Equals<
readonly [] | readonly ["Emit"] | readonly ["Save"] | readonly ["Save", "Emit"] | readonly ["Quote"] | readonly ["Quote", "Emit"] | readonly ["Quote", "Save"] | readonly ["Quote", "Save", "Emit"],
_000004[number]
>>
// This is ok
const x = {
actions: ["Quote"]
} as const;
type _000006 = Assert<CompatibleWith<typeof x, {actions: ReadonlyCombinationsOf<ActionNames>[number]}>>
// This is not and in our case dosn't make sense
const y = {
actions: ["Quote", "Quote"]
} as const
type _000007 = AssertNot<CompatibleWith<typeof y, {actions: ReadonlyCombinationsOf<ActionNames>[number]}>>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment