Last active
January 14, 2022 18:21
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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