00869-extreme-distributeunions

Back

The function is fully unwrapped in the following way:

  1. Deeply distribute over the function's return type.
  2. Transform union to intersection on the deeply distributed arguments.

The function is intersected with the distributed arguments to compliment arguments' contravariance. Since intersected functions are currently quite limiting, skipping step 2 may be desired in which case F can be returned directly.

type Unknowns = unknown[];
type UnknownRecord = Record<keyof never, unknown>;
type UnknownFunction = (...args: never[]) => unknown;

type As<T, _Infer extends T> = unknown;
type Merge<T> = {
  [K in keyof T]: T[K]
} & unknown;
type UnionToIntersection<T> = (T extends unknown
    ? (x: T) => never
    : never
) extends (x: infer R) => unknown
    ? R
    : never
;


type DistributeTuple<T extends Unknowns> = T extends [infer Head, ...infer Tail]
  ? DistributeUnions<Head> extends infer H
    ? H extends unknown
      ? [H, ...DistributeTuple<Tail>]
      : never
    : never
  : []
;
type DistributeRecord<T extends UnknownRecord> = Merge<unknown extends As<keyof T, infer K>
    ? [K] extends [never]
        ? {}
        : K extends unknown
            ? DistributeUnions<T[K]> extends infer V
                ? V extends unknown
                    ? Record<K, V> & DistributeRecord<Omit<T, K>>
                    : never
                : never
            : never
    : never
>;
type DistributeFunction<T extends UnknownFunction> = unknown extends As<
        DistributeUnions<ReturnType<T>> extends infer R
            ? R extends unknown
                ? (...args: Parameters<T>) => R
                : never
            : never,
        infer F
    >
    ? F extends unknown
        ? UnionToIntersection<
            unknown extends As<DistributeUnions<Parameters<F>>, infer A>
                ? A extends unknown
                    ? (...args: A) => ReturnType<F>
                    : never
                : never
        >
        : never
    : never
;
type DistributeUnions<T> = T extends Unknowns
  ? DistributeTuple<T>
  : T extends UnknownRecord
    ? DistributeRecord<T>
    : T extends UnknownFunction
        ? DistributeFunction<T>
        : T
;

Solution by codpro2005 #37773

type DistributeUnions<
  T,
  TupleRes extends any[] = [],
  ObjectRes extends object = {}
> = T extends any[]
  ? T extends [infer First, ...infer Rest]
    ? DistributeUnions<First> extends infer E
      ? E extends any
        ? DistributeUnions<Rest, [...TupleRes, E], never>
        : never
      : never
    : TupleRes
  : T extends object
  ? {} extends T
    ? { [P in keyof ObjectRes]: ObjectRes[P] }
    : keyof T extends infer K
    ? K extends keyof T
      ? DistributeUnions<T[K]> extends infer V
        ? V extends any
          ? DistributeUnions<Omit<T, K>, never, ObjectRes & Record<K, V>>
          : never
        : never
      : never
    : never
  : T;

大致思路:

处理前先分三类:元组、对象、其他类型,为实现尾递归,所以需要 TupleRes ObjectRes 分别存储对应类型的结果。

关键点:先递归到最底层,再进行分布式操作,否则只能分布式操作最外层。

Solution by Barrenboat #37425

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

type LastOfUnion<U> = UnionToIntersection<
  U extends U ? (arg: U) => void : never
> extends (arg: infer Last) => void
  ? Last
  : never;

type DistributeArray<A extends any[]> = A extends [infer Head, ...infer Tail]
  ? Head extends Head
    ? [Head, ...DistributeArray<Tail>]
    : never
  : [];

type DistributeObjectByKey<T, K extends keyof T, V = T[K]> = V extends V
  ? { [Key in keyof T]: Key extends K ? V : T[Key] }
  : never;

type DistributeObject<T, R = T, Last = LastOfUnion<keyof T>> = {} extends T
  ? R
  : DistributeObject<
      Omit<T, Last & keyof T>,
      DistributeObjectByKey<R, Last & keyof R>
    >;

type DistributeUnions<T> = T extends [infer Head, ...infer Tail]
  ? DistributeArray<[DistributeUnions<Head>, ...DistributeUnions<Tail>]>
  : T extends object
  ? DistributeObject<{ [Key in keyof T]: DistributeUnions<T[Key]> }>
  : T;

Solution by user65536 #36469

// 整体思路:利用联合类型可以分发的特性,分别处理数组和对象,数组可以用 [infer F, ...infer Rest] 来遍历每一项再递归分发再组合, 对象通过取到每一项key,用Omit<K, key> 去遍历每一项,最后再组合

type IsNever<T> = [T] extends [never] ? true : false

type DistributeUnions<T> =
  T extends unknown[] ? DistributeArray<T>
  : T extends object ? DistributeObject<T>
    : T

type JoinArray<F, R extends unknown[]> =
  F extends any ? [F, ...R] : never

type DistributeArray<T extends unknown[]> =
  T extends [infer F, ...infer Rest] ? JoinArray<DistributeUnions<F>, DistributeArray<Rest>> : []

type HandleObjectChild<V, K extends keyof any> =
  V extends any ? { [key in K]:  V } : never

//  处理对象某一属性及其子对象
type HandleObjectItem<T, K extends keyof T, V = T[K]> =
  V extends any ? HandleObjectChild<DistributeUnions<V>, K> : never

// 处理对象中剩余属性
type HandleObjectRest<T, K extends keyof T, U extends keyof T = K> =
  IsNever<Exclude<U, K>> extends true ? {} : DistributeObject<T, Exclude<U, K>>

// 合并对象
type JoinObject<T, O> =
  T extends any ?
    O extends any ? { [key in keyof T | keyof O]: key extends keyof T ? T[key] : key extends keyof O ? O[key] : never } : never : never

// 遍历对象
type DistributeObject<T, K extends keyof T = keyof T, U extends keyof T = K> =
  K extends any ? JoinObject<HandleObjectItem<T, K>, HandleObjectRest<T, K, U>>  : never

Solution by moonshadow-miao #34226

type Param<F> = [F] extends [(p: infer P) => void] ? P : never;
type Intersection<T> = Param<T extends unknown ? (p: T) => void : never>;

type Pick1<T, K extends keyof T, V> = [V extends V ? {[K1 in K]: V} : never];

type DistrObj<T, K extends keyof T = keyof T> = (Intersection<K extends K ? Pick1<T, K, Distr<T[K]>> : never> & [unknown])[0];

type DistrArr<A extends unknown[], R extends unknown[] = []> = A extends [infer F, ...infer T]
    ? Distr<F> extends infer Fi ? Fi extends Fi ? DistrArr<T, [...R, Fi]> : never : never
    : R;

type Distr<T> = T extends unknown[] ? DistrArr<T> : T extends object ? DistrObj<T> : T;

type Norm<T> = T extends object ? {[K in keyof T]: Norm<T[K]>} : T;

type DistributeUnions<T> = Norm<Distr<T>>;

Solution by alexandroppolus #30996

type Union2Intersection<T> = (T extends T ? (arg: T) => any : never) extends (arg: infer P) => any ? P : never;
type UnionLast<T> = Union2Intersection<T extends T ? () => T : never> extends () => infer R ? R : never;
type Union2Array<T, _TL = UnionLast<T>> = [T] extends [never] ? [] : [...Union2Array<Exclude<T, _TL>>, _TL];
// type IsUnion<T, U = T> = U extends T ? [Exclude<T, U>] extends [never] ? false : true : false;
type ExtractObj<T> =
  T extends [] ? [] :
  T extends [infer F, ...infer R extends any[]] ? [ExtractObj<F>, ...ExtractObj<R>] : T extends object ? T[keyof T] : T;

/**数组交叉 */
type UnionTuplesCross<T extends any[], _DistributeObj extends boolean = true> =
  T extends [infer TF, ...infer TR] ?
  [TF] extends [never] ? never :
  [UnionLast<TF> extends object ? _DistributeObj extends true ? DistributeUnions<UnionLast<TF>> : UnionLast<TF> : UnionLast<TF>, ...UnionTuplesCross<TR>] | UnionTuplesCross<[Exclude<TF, UnionLast<TF>>, ...TR]>  // ×[1 | 2, 'a' | 'b', false | true] ==> [1, ...×['a' | 'b', false | true]] | [2, ...×['a' | 'b', false | true]] ==> [1, ...( [`a`, ...×[false | true] | [`b`, ...×[false | true] )]] | [2, ...( [`a`, ...×[false | true] | [`b`, ...×[false | true] )]]
  : [];

/**结构体部分 */
namespace someComplicatedHelper {
  type ObjectTypes2Array<T extends object, _Single extends object = { [P in keyof T]: { [K in P]: T[K] extends object ? UnionObjectsCross<T[K]> : T[K] } }> = Union2Array<_Single[keyof _Single]>;
  type ToKeyTypesObject<T extends object, _TUL = UnionLast<ExtractObj<T>>> = { [P in keyof T]: _TUL } | ([Exclude<ExtractObj<T>, _TUL>] extends [never] ? never : ToKeyTypesObject<{ [P in keyof T]: Exclude<ExtractObj<T>, _TUL> }>);
  type KTOA<T extends any[]> = T extends [infer F extends object, ...infer R extends object[]] ? [ToKeyTypesObject<F>, ...KTOA<R>] : [];
  type CombObj<T extends object | unknown> = { [P in keyof T]: T[P] };
  type CombSingleObjsCross<T extends object[]> = T extends [infer F, ...infer R extends object[]] ? F & CombSingleObjsCross<R> : unknown;
  type ObjectUnionCross<T extends object> = CombObj<CombSingleObjsCross<UnionTuplesCross<KTOA<ObjectTypes2Array<T>>, false>>>

  /**结构体交叉 */
  export type UnionObjectsCross<T extends object, _UL = UnionLast<T>> =
    [T] extends [never] ? never : ObjectUnionCross<_UL extends object ? _UL : never> | UnionObjectsCross<Exclude<T, _UL>>;
}

type DistributeUnionsInner<T> = T extends any[] ? UnionTuplesCross<T> :
  T extends object ? someComplicatedHelper.UnionObjectsCross<T> :
  T;

type DistributeUnions<T> = T extends any[] ? DistributeUnionsInner<DistributeUnionsInner<T>> : DistributeUnionsInner<T>;

Solution by E-uler #25793

// your answers
type DistributeTuple<T extends unknown[]> = 
  T extends [infer Head, ...infer Rest]
  ? DistributeUnions<Head> extends infer Value
    ? Value extends unknown
      ? [Value, ...DistributeTuple<Rest>]
      : never
    : never
  : T

type DistributeRecord<T extends Record<PropertyKey, unknown>, _Key extends keyof T = keyof T> = 
  T extends Record<PropertyKey, never> ? {} : 
  _Key extends unknown
  ? DistributeUnions<T[_Key]> extends infer Value
    ? Value extends unknown
      ? DistributeRecord<Omit<T, _Key>> extends infer Rest
        ? Rest extends unknown
          ? {[P in keyof T]: P extends keyof Rest ? Rest[P] : Value}
          : never
        : never
      : never
    : never
  : never

type DistributeUnions<T> = 
  T extends unknown[]
  ? DistributeTuple<T>
  : T extends Record<PropertyKey, unknown>
    ? DistributeRecord<T>
    : T

Solution by jxhhdx #24544

Version 1: It works

type DistributeTuple<T extends unknown[]> = 
  T extends [infer Head, ...infer Rest]
  ? DistributeUnions<Head> extends infer Value
    ? Value extends unknown
      ? [Value, ...DistributeTuple<Rest>]
      : never
    : never
  : T

type DistributeRecord<T extends Record<PropertyKey, unknown>, _Key extends keyof T = keyof T> = 
  T extends Record<PropertyKey, never> ? {} : 
  _Key extends unknown
  ? DistributeUnions<T[_Key]> extends infer Value
    ? Value extends unknown
      ? DistributeRecord<Omit<T, _Key>> extends infer Rest
        ? Rest extends unknown
          ? {[P in keyof T]: P extends keyof Rest ? Rest[P] : Value}
          : never
        : never
      : never
    : never
  : never

type DistributeUnions<T> = 
  T extends unknown[]
  ? DistributeTuple<T>
  : T extends Record<PropertyKey, unknown>
    ? DistributeRecord<T>
    : T

playground for version 1

Version 2: More effecient algorithm

type UnionToIntersection<T> = (T extends any ? (x: T) => void : never) extends (x: infer U) => void ?  U : never
type PopUnion<T> = UnionToIntersection<T extends any ? (x: T) => void : never> extends (x: infer U) => void ? U : never
type OneAndRest<T> = [PopUnion<T>, Exclude<T, PopUnion<T>>]

type DistributeTuple<T extends unknown[]> = 
  T extends [infer Head, ...infer Rest]
  ? DistributeUnions<Head> extends infer Value
    ? Value extends unknown
      ? [Value, ...DistributeTuple<Rest>]
      : never
    : never
  : T

type DistributeRecord<T extends Record<PropertyKey, unknown>> = 
  T extends Record<PropertyKey, never> ? {} : 
  OneAndRest<keyof T> extends [infer Key extends keyof T, infer RestKeys extends keyof T]
  ? DistributeUnions<T[Key]> extends infer Value
    ? Value extends unknown
      ? DistributeRecord<Pick<T, RestKeys>> extends infer RestRecord
        ? RestRecord extends unknown
          ? {[P in keyof T]: P extends keyof RestRecord ? RestRecord[P] : Value}
          : never
        : never
      : never
    : never
  : never

type DistributeUnions<T> = 
  T extends unknown[]
  ? DistributeTuple<T>
  : T extends Record<PropertyKey, unknown>
    ? DistributeRecord<T>
    : T

playground for version 2

Solution by zhaoyao91 #23086

// Tuple ====================
type _DistributeTuple<T extends unknown[]> =
  T extends [ infer F, ...infer R ]
    ? F extends F
      ? _DistributeNestForTuple<DistributeUnions<F>, _DistributeTuple<R>>
      : never
    : []

type _DistributeNestForTuple<T, Rest extends unknown[]> = T extends T ? [ T, ..._DistributeTuple<Rest>] : never
// Tuple ====================


// Object ===================
type _Never2Unknown<T> = [T] extends [never] ? unknown : T

type _CombineObject<T, _T extends T = T> =
(T extends T ? T & _Never2Unknown<Exclude<_T, Pick<_T, keyof T>>> : never) extends infer I
  ? { [K in keyof I]: I[K] }
  : never

type _DistributeByKey<T, K extends string> = T extends T ? { [_K in K]: T } : never

type _DistributeSingleObject<T extends object, _K extends keyof T = keyof T> =
  _K extends _K
    ? T[_K] extends infer V
      ? V extends object
        ? _DistributeByKey< // For nesting
        _CombineObject<_DistributeSingleObject<V>>,
        _K & string
        >
        : { [__K in _K]: V }
      : never
    : never

type _DistributeObject<T extends object> = T extends T ? _CombineObject<_DistributeSingleObject<T>> : never
// Object ===================


// Main ====================
type DistributeUnions<T> = T extends unknown[]
  ? _DistributeTuple<T>
  : T extends object
    ? _DistributeObject<T>
    : T
// Main ====================

Solution by lvjiaxuan #23028

// your answers
// 把前面几位的代码翻看了几十遍后模仿着写了一边
// Merze { } & { } => {...} 
// UnionToIntersection { } | { } => { } & { } 
// DistObj { ... , ... } => { _temp:{} } | { _temp:{} } => { _temp:{} } & { _temp:{} } => {} & {}
// ----------------------------------------------------------------------------
type Merze<T> = T extends Function ? T : { [K in keyof T]: T[K] };
// ----------------------------------------------------------------------------
type UnionToIntersection<U>
    = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
// ----------------------------------------------------------------------------
type ValueExt<K, V> = V extends V ? { [_ in K & string]: V } : never
// ----------------------------------------------------------------------------
type DistObj<T, K extends keyof T = keyof T> = Merze<
    UnionToIntersection<
        K extends K ? { _temp: ValueExt<K, DistributeUnions<T[K]>> } : never
    > extends { _temp: infer O } ? O : {}>
// ----------------------------------------------------------------------------
type ArrExt<A extends any[], U> = A extends A ? U extends U ? [U, ...A] : never : never
// ----------------------------------------------------------------------------
type DistArr<T> = T extends [infer U, ... infer L] ? ArrExt<DistArr<L>, DistributeUnions<U>> : []
// ----------------------------------------------------------------------------
type DistributeUnions<T>
    = T extends readonly any[]
    ? DistArr<T>
    : T extends Function
    ? T // 这个似乎还得完善一下。。。
    : T extends { [_: string]: any }
    ? DistObj<T>
    : T
// ----------------------------------------------------------------------------

Solution by goddnsgit #22276

// your answers
type Equals<
    A,B> =(<X>()=>X extends A ? 1 : 2) extends (<Y>()=>Y extends B ? 1 : 2) 
    ? true 
    : false ;

type UnionToIntersecton<
    U > =
    [U] extends [never]
    ? never
    :(U extends U ? (a:U)=>any : never) extends (a:infer R)=>any ? R : never;

type UnionToReturnType<
    U > =
    U extends U 
    ? (...a:any)=>U
    : never;

type ResultType<
    U > =
    U extends (...a:any)=>infer R ? R : any   

type LastOfUnion<
    U > =
    ResultType<UnionToIntersecton<UnionToReturnType<U>>>

type DistributeValue<
    T extends object,
    Key extends keyof T,
    Value = T[Key]> =
    Value extends Value 
    ? {[J in keyof T] : J extends Key ? DistributeMemberOnce<Value> : T[J]} 
    : never ;

type DistributeMemberOnce<
    T,
    Keys extends keyof T = keyof T,
    LastKey extends keyof T = LastOfUnion<Keys>&keyof T > =  
     T extends object
    ? [LastKey] extends [never]
    ? T
    : DistributeMemberOnce<DistributeValue<T,LastKey>,Exclude<Keys,LastKey>>
    : T ;

type DistributeUnionOnce<
    T > =
    T extends T
    ? DistributeMemberOnce<T>
    : never  ;

type DistributeUnions<
    T > =
    Equals<T,DistributeUnionOnce<T>> extends true
    ? T
    : DistributeUnions<DistributeUnionOnce<T>>  ;

Solution by justBadProgrammer #16841

type DistributeUnions<T>
  = T extends unknown[] ? DistributeArray<T>
  : T extends object ? Merge<DistributeObject<T>>
  : T

type DistributeArray<A extends unknown[]>
  = A extends [infer H, ...infer T]
  ? ArrHelper<DistributeUnions<H>, T>
  : []
type ArrHelper<H, T extends unknown[]> = H extends H ? [H, ...DistributeArray<T>] : never

type DistributeObject<O extends object, K extends keyof O = keyof O>
  = [K] extends [never] ? {}
  : K extends K ? ObjHelper<K, DistributeUnions<O[K]>> & DistributeObject<Omit<O, K>>
  : never
type ObjHelper<K, V> = V extends V ? { [k in K & string]: V } : never

type Merge<O> = { [K in keyof O]: O[K] }

Solution by Alexsey #11761

type DistributeUnions<T> = [T] extends [never] ? never : T extends T ? Distribute<{[K in keyof T]: DistributeUnions<T[K]>}> : never

type Distribute<T, K extends keyof T = PickOne<T>> = [K] extends [never] ? T : DistributeByValue<T, K, T[K]>

type DistributeByValue<T, K extends keyof T, V> = [V] extends [never] ? never : V extends V ? DistributeUnions<{[P in keyof T]: P extends Keys<T> ? DistributeUnions<P extends K ? V : T[P]> : T[P]}> : never

type PickOne<T> = ExtractOne<{[K in Keys<T>]: Set<T[K & Keys<T>]> extends Set<infer I> ? I extends I ? [Exclude<T[K], I>] extends [never] ? never : K : never : never}[Keys<T>]>

type ExtractOne<U> = (U extends U ? (_: Promise<U>) => void : never) extends (_: infer I extends Promise<U>) => void ? Awaited<I> : never

type Keys<T> = T extends unknown[] ? keyof T & `${number}` : keyof T

Playground

Solution by teamchong #11515

Playground to test the solution

type Resolve<T> = T extends Function ? T : { [K in keyof T]: T[K] };

type LooseLookup<T, K extends PropertyKey> = T[K & keyof T];

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

type _DistributeUnions<T> = {
  [K in keyof T]: {
    PROP: T[K] extends infer U
      ? U extends any
        ? { [KK in K]: U }
        : never
      : never;
  };
};

type DistributeUnions<
  T,
  DT = { [K in keyof T]: DistributeUnions<T[K]> }
> = DT extends object
  ? Resolve<
      LooseLookup<UnionToIntersection<_DistributeUnions<DT>[keyof DT]>, "PROP">
    > extends infer RES
    ? RES extends any
      ? DT extends readonly any[]
        ? { [K in keyof DT]: LooseLookup<RES, K> }
        : RES
      : never
    : never
  : DT;

Solution by jfet97 #11311

type PopUnion<T> = UnionToIntersection<T extends any ? (x: T) => 1 : never> extends (x: infer U) => 1 ? U : never
type UnionToIntersection<T> = (T extends any ? (x: T) => 1 : never) extends (x: infer U) => infer R ?  U : never
type UnionToTuple<T, U = PopUnion<T>> = [T] extends [never] ? [] : [U, ...UnionToTuple<Exclude<T, U>>]

type Merge<T> = {[P in keyof T]: T[P]}

type DistributeObject<T, U = UnionToTuple<keyof T>>
    = U extends [infer L, ...infer R]
        ? DistributeUnions<T[keyof T & L]> extends infer V
            ? V extends any ? Merge<{[P in keyof T & L]: V} & DistributeObject<T, R>>: never
        : never
    : {}

type DistributeArray<T> 
    = T extends [infer L, ...infer R]
        ? DistributeUnions<L> extends infer U
            ? U extends any ? [U, ...DistributeArray<R>] : never
        : never
    : []

type DistributeUnions<T> 
    = T extends any
        ? T extends any[] ? DistributeArray<T>
        : T extends object ? DistributeObject<T>
        : T
    : never

Solution by okayu29 #6309

TS Playground.

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;

Solution by uid11 #901