koSakano/type-challenges

869 - DistributeUnions

Opened this issue · 0 comments

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
    ? I
    : never;

type UnionToTuple<T> = UnionToIntersection<T extends any ? (t: T) => T : never> extends (
    _: any,
) => infer W
    ? [...UnionToTuple<Exclude<T, W>>, W]
    : [];

type Tuple = readonly unknown[];
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type ToString<T extends number> = `${T}`;
type StringDigit = ToString<Digit>;
type StringNumber = `${StringDigit}${string}`;

/**
 * IsTuple<0[]> = false.
 * IsTuple<[0]> = true.
 */
type IsTuple<T> = T extends Tuple ? (number extends T['length'] ? false : true) : false;

/**
 * SetValue<[1, 2, 3], '1', 4 | 5> = [1, 4, 3] | [1, 5, 3].
 * SetValue<{ foo: 2 }, 'bar', 4 | 5> = { foo: 2, bar: 4} | { foo: 2, bar : 5 }.
 */
type SetValue<T, Key extends string, Value> = Value extends unknown
    ? IsTuple<T> extends true
        ? {
              [K in keyof T]: K extends Key ? Value : T[K];
          }
        : {
              [K in Key | keyof T]: K extends Key ? Value : K extends keyof T ? T[K] : never;
          }
    : never;

/**
 * Tail<[1, 2, 3]> = [2, 3].
 */
type Tail<T extends Tuple> = T extends readonly [unknown, ...infer Rest] ? Rest : [];

/**
 * KeysUnion<{ foo: 1, bar: 2 }> = "foo" | "bar".
 * KeysUnion<[3, 4, 5]>; = "0" | "1" | "2".
 */
type KeysUnion<T> = IsTuple<T> extends true ? StringNumber & keyof T : string & keyof T;

/**
 * Keys<[1 | 2]> = ["0"].
 */
type Keys<T, K = UnionToTuple<KeysUnion<T>>> = K extends ReadonlyArray<KeysUnion<T>> ? K : never;

type Reduce<T, K extends ReadonlyArray<string & keyof T>> = T extends unknown
    ? K extends []
        ? T
        : Reduce<SetValue<T, K[0], DistributeUnions<T[K[0]]>>, Tail<K>>
    : never;

type DistributeUnions<T> = T extends object ? Reduce<T, Keys<T>> : T;