90 lines
3.3 KiB
TypeScript
90 lines
3.3 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
// required `any` for Distributive Conditional
|
|
// https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
|
|
|
|
/**
|
|
* Transform a union to a tuple
|
|
* from 'a' | 'b' | ['c', 'd'] to `['a', 'b', ['c', 'd']]`
|
|
*/
|
|
export type ToTuple<Union> = ToTupleRec<Union, []>;
|
|
|
|
// Recursively build a tuple from a union
|
|
type ToTupleRec<Union, Result extends any[]> = SpliceOne<Union> extends never
|
|
? [ExtractOne<Union>, ...Result]
|
|
: ToTupleRec<SpliceOne<Union>, [ExtractOne<Union>, ...Result]>;
|
|
|
|
// Remove the first element of union
|
|
type SpliceOne<Union> = Exclude<Union, ExtractOne<Union>>;
|
|
|
|
// Extract the first element of union
|
|
type ExtractOne<Union> = ExtractParam<UnionToIntersection<UnionToParam<Union>>>;
|
|
|
|
/**
|
|
* Extract param of function
|
|
*
|
|
* Here, used with an intersection of functions generated by UnionToIntersection to pick the first type of intersection
|
|
*
|
|
* @example
|
|
* type EP = ExtractParam<((k: 'a') => void) & ((k: 'b') => void)> // 'a'
|
|
*/
|
|
type ExtractParam<F> = F extends { (a: infer A): void } ? A : never;
|
|
|
|
/**
|
|
* When called with a union of functions, allows to generate an intersection of the functions params types
|
|
*
|
|
* ---
|
|
*
|
|
* In our usage
|
|
* ```
|
|
* type Inter = UnionToIntersection<UnionToParam<'a' | 'b'>>;
|
|
* // equals
|
|
* type Inter = UnionToIntersection<((k: 'a') => void) | ((k: 'b') => void)>;
|
|
* // which expands to
|
|
* type Inter =
|
|
* | ((k: (k: 'a') => void) => void)
|
|
* | ((k: (k: 'b') => void) => void) extends (k: infer I) => void
|
|
* ? I
|
|
* : never;
|
|
* // using the contra-variant positions, an intersection is inferred. The result is then
|
|
* type Inter = ((k: 'a') => void) & ((k: 'b') => void);
|
|
* // (infer I) of 1st Union elem & (infer I) of 2nd Union element
|
|
* ```
|
|
*
|
|
* ---
|
|
*
|
|
* For more details see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#type-inference-in-conditional-types
|
|
*
|
|
* ```
|
|
* // The following example demonstrates how multiple candidates for the same type variable in co-variant positions causes a union type to be inferred:
|
|
* type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
|
|
* type T10 = Foo<{ a: string; b: string }>; // string
|
|
* type T11 = Foo<{ a: string; b: number }>; // string | number
|
|
*
|
|
* // Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:
|
|
* type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never;
|
|
* type T20 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; // string
|
|
* type T21 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; // string & number
|
|
* ```
|
|
*
|
|
*/
|
|
type UnionToIntersection<U> = UnionToParam<U> extends (k: infer I) => void
|
|
? I
|
|
: never;
|
|
|
|
/**
|
|
* Transform T to `(k: T) => void` (excluding never)
|
|
*
|
|
* When called with a union of functions, generates a union of functions taking a function as param
|
|
*
|
|
* @example
|
|
* type U = UnionToParam<'a' | 'b'>;
|
|
* // = ((k: "a") => void) | ((k: "b") => void)
|
|
*
|
|
* @example
|
|
* type U2 = UnionToParam<UnionToParam<'a' | 'b'>>;
|
|
* // = ((k: (k: "a") => void) => void) | ((k: (k: "b") => void) => void)
|
|
*/
|
|
type UnionToParam<U> = U extends any ? (k: U) => void : never;
|
|
|
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|