00730-hard-union-to-tuple

Back

先说结论:对于函数联合类型推断函数参数时,结果会是 这两个函数参数类型的交叉类型,相关概念:协变

type FnA = (a: 1) => void
type FnB = (b: 2) => void

type Result1 = (FnA | FnB ) extends (a: infer R) => void ? R : never;  // 结果会是 1 & 2,  即never

那如果函数参数是本身就是函数呢

type FnA = (a: (arg: 1) => void) => void
type FnB = (b: (arg: 2) => void) => void

type Result1 = (FnA | FnB ) extends (a: infer R) => void ? R : never;  
// 结果当然是这两个函数的交叉类型 ((arg: 1) => void) & ((arg: 2) => void)

那对交叉类型的两个函数进行参数推断结果会是什么呢?

type Result2 = ((arg: 1) => void) & ((arg: 2) => void) extends (arg: infer R) => void ? R: never; 
// 结果会是 2, 个人猜测1或者2都是符合条件的,可能根据编译器的优先级有关,在我本机上结果是2

这一步我们就提取了联合类型的某一个类型了,再进行递归,就可以把剩下的类型全部提出出来。

本题解答:

type UnionToTuple<T, Last = UnionLast<T>> = [T] extends [never]
  ? []
  : Last extends T
  ? [...UnionToTuple<Exclude<T, Last>>, UnionLast<Last>]
  : [];

type IntersectionFunction<T> = (
  T extends any ? (a: (b: T) => void) => void : never
) extends (a: infer R) => void
  ? R
  : never;

type UnionLast<T> = IntersectionFunction<T> extends (a: infer R) => void
  ? R
  : never;

Solution by Vampirelee #32625

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

type LastOfUnion<U> = 
  UnionToIntersection<
  (U extends unknown ? (arg: U) => unknown : never)
  > extends (arg: infer I) => unknown ? I : never

type IsAny<T> = [T] extends [never] ? 
  false :
  T extends (1 & T) ? 
  true : unknown extends T ? 
  true : false

type IsUnion<T, Copy = T> = 
  [T] extends [never] ? 
  false :
  T extends T ?
  [Copy] extends [T] ?
  // using isAny here because of 
  // specific type-cases
  IsAny<T> extends true ?
  true :
  false :
  true :
  false

type UnionToTupleImpl<U, R extends unknown[] = []> =
 [U] extends [never] ? 
 R :
 UnionToTupleImpl<Exclude<U, LastOfUnion<U>>, [...R, LastOfUnion<U>]>

type UnionToTuple<U> =
  [U] extends [never] ? 
  never :
  IsUnion<U> extends false ? 
  U :
  UnionToTupleImpl<U>

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type ExtractValuesOfTuple<T extends any[]> = T[keyof T & number]

type cases = [
  Expect<Equal<UnionToTuple<'a' | 'b'>['length'], 2>>,
  Expect<Equal<ExtractValuesOfTuple<UnionToTuple<'a' | 'b'>>, 'a' | 'b'>>,
  // Expect<Equal<ExtractValuesOfTuple<UnionToTuple<'a'>>, 'a'>>,
  Expect<Equal<ExtractValuesOfTuple<UnionToTuple<any>>, any>>,
  Expect<Equal<ExtractValuesOfTuple<UnionToTuple<undefined | void | 1>>, void | 1>>,
  Expect<Equal<ExtractValuesOfTuple<UnionToTuple<any | 1>>, any | 1>>,
  Expect<Equal<ExtractValuesOfTuple<UnionToTuple<any | 1>>, any>>,
  Expect<Equal<ExtractValuesOfTuple<UnionToTuple<'d' | 'f' | 1 | never>>, 'f' | 'd' | 1>>,
  Expect<Equal<ExtractValuesOfTuple<UnionToTuple<[{ a: 1 }] | 1>>, [{ a: 1 }] | 1>>,
  Expect<Equal<ExtractValuesOfTuple<UnionToTuple<never>>, never>>,
  Expect<Equal<ExtractValuesOfTuple<UnionToTuple<'a' | 'b' | 'c' | 1 | 2 | 'd' | 'e' | 'f' | 'g'>>, 'f' | 'e' | 1 | 2 | 'g' | 'c' | 'd' | 'a' | 'b'>>,
]

Solution by gearonix #31874

type UnionToIntersection=(T extends T?(arg:T)=>unknown:never) extends (arg:infer R)=>unknown?R:never

type GetLastUnion=UnionToIntersection<T extends T?()=>T:never> extends ()=>infer R?R:never

type UnionToTuple<T,Last=GetLastUnion> =[T] extends [never]?[]:[...UnionToTuple<Exclude<T,Last>>,Last]

Solution by DoubleWoodLin #28580

//需要了解性质:多个函数交集的返回值类型只取最后一个!(This is Important!)
//例如:
// type Intersepted = (() => 'a') & (() => 'b') & (() => 'c')
// type Last = Intersepted extends () => infer R ? R : never // 'c'
//参考:https://github.com/type-challenges/type-challenges/issues/21658#issue-1523555097

/**并集转交集 */
type UnionToIntersection<T> = (T extends T ? (args: T) => any : never) extends (args: infer P) => any ? P : never;    // a | b | c ==> a & b & c
/**联合类型最后一个 */
type UnionLast<T> = (UnionToIntersection<T extends T ? () => T : never>) extends () => infer R ? R : never;           // a | b | c ==> (()=>a) | (()=>b) | (()=>c) ==> (()=>a) & (()=>b) & (()=>c) ==> c

type UnionToTuple<T> = [T] extends [never] ? [] : [UnionLast<T>, ...UnionToTuple<Exclude<T, UnionLast<T>>>];

参考:https://github.com/type-challenges/type-challenges/issues/21658#issue-1523555097

Solution by E-uler #24894

// your answers
type UnionToIntersection<U> = (U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown ? R : never;
export type UnionToTuple<T> = 
  UnionToIntersection<
      T extends any ? () => T : never
  > extends () => infer ReturnType
      ? [...UnionToTuple<Exclude<T, ReturnType>>, ReturnType]
      : [];

Solution by jxhhdx #23736

type UnionToFnInserction<T> = (T extends any ? (arg: () => T) => any : never) extends (arg: infer P) => any ? P : never
type UnionToTuple<T, A extends any[] = []> = UnionToFnInserction<T> extends () => infer R ? UnionToTuple<Exclude<T, R>, [R, ...A]> : A

Solution by snakeUni #23660

type IsEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false
type IsUnion<T, B = T> = T extends B ? ([B] extends [T] ? false : true) : false


type NumberUnionToTuple<U, N extends number[] = [], Result extends any[] = []> = 
  IsUnion<U> extends false
  ? [U] extends [never]
    ? Result
    : [...Result, U]
  : IsEqual<U, N['length']> extends true
  ? [...Result, U]
  : NumberUnionToTuple<
    U extends N['length'] ? never : U,
    [...N, 0],
    N['length'] extends U ? [...Result, N['length']] : Result
  >

type t0 = NumberUnionToTuple<2> // [2]
type t1 = NumberUnionToTuple<2 | 3 | 5> //  [2, 3, 5]
type t2 = NumberUnionToTuple<100 | 200 | 300 | 500> //  [100, 200, 300, 500]
type t3 = NumberUnionToTuple<10 | 5 | 20 | 888 | 30 | 66 | 500> // [5, 10, 20, 30, 66, 500, 888]

Solution by TKBnice #23588

type IsEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false
type IsUnion<T, B = T> = T extends B ? ([B] extends [T] ? false : true) : false


type UnionToTuple<U, N extends number[] = [], Result extends any[] = []> = 
  IsUnion<U> extends false
  ? [U] extends [never]
    ? Result
    : [...Result, U]
  : IsEqual<U, N['length']> extends true
  ? [...Result, U]
  : UnionToTuple<
    U extends N['length'] ? never : U,
    [...N, 0],
    N['length'] extends U ? [...Result, N['length']] : Result
  >

type t0 = UnionToTuple<2> // [2]
type t1 = UnionToTuple<2 | 3 | 5> //  [2, 3, 5]
type t2 = UnionToTuple<100 | 200 | 300 | 500> //  [100, 200, 300, 500]
type t3 = UnionToTuple<10 | 5 | 20 | 888 | 30 | 66 | 500> // [5, 10, 20, 30, 66, 500, 888]

Solution by TKBnice #23587

type UnionToCross<T> = (T extends T ? (s: () => T) => void : never) extends (
  s: infer R
) => void
  ? R
  : never;

type GetCrossLast<T> = T extends () => infer R ? R : never;

type UnionToTuple<T, Result extends Array<any> = []> = [T] extends [never]
  ? Result
  : [
      ...UnionToTuple<Exclude<T, GetCrossLast<UnionToCross<T>>>>,
      GetCrossLast<UnionToCross<T>>
    ];

Solution by so11y #21225

// your answers
type UnionToIntersectionFn<U> = (
  U extends unknown ? (k: () => U) => void : never
) extends (k: infer I) => void ? I : never;

type GetUnionLast<U> = UnionToIntersectionFn<U> extends () => infer I 
  ? I : never;

type Prepend<Tuple extends unknown[], First> = [First, ...Tuple];

type UnionToTuple<
  Union, 
  T extends unknown[] = [], 
  Last = GetUnionLast<Union>
> = [Union] extends [never] 
  ? T 
  : UnionToTuple<Exclude<Union, Last>, Prepend<T, Last>>;

Solution by YqxLzx #21113

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

type Test = UnionToTuple<'a' | 'b'>
//  ^?

Solution by XkSuperCool #20764

type UnionToFnInsertion<T> = (T extends any
  ? (arg: () => T) => any : never) extends (arg: infer P) => any
    ? P
    : never

type UnionToTuple<T> = UnionToFnInsertion<T> extends () => infer R
    ? [...UnionToTuple<Exclude<T, R>>, R]
    : []

Solution by pengzhanbo #20475

// 参考:https://github.com/type-challenges/type-challenges/issues/10191

// 1. 将联合类型转换成对应的函数交叉类型 (arg的参数是函数,返回的类型才是交叉类型,其他的都是never)
type UnionToIntersectionFn<U> = (U extends unknown ? (arg: () => U) => void : never) extends (arg: infer I) => void ? I : never;

// 2. 获取联合类型的最后一个类型
// (() => "a") & (() => "b") & (() => "c")
// 在获取函数的返回值上,函数重载和函数交叉类型是一样的
type GetUnionLast<U> = UnionToIntersectionFn<U> extends () => infer I ? I : never;

// 3. 联合类型转换为元组
type UnionToTuple<U, Last = GetUnionLast<U>> = [U] extends [never] ? [] : [...UnionToTuple<Exclude<U, Last>>, Last];

Solution by CaoXueLiang #19059

// your answers

// an intersection of function types is considered the same as an overloaded function with multiple call signatures
// and when TypeScript using type inference on an overloaded function type, It just uses the last call signature and ignores all the rest of them


type UnionToFnInserction<T> = (T extends any ? (arg: () => T) => any : never) extends (arg: infer P) => any ? P : never
type UnionToTuple<T, A extends any[] = []> = UnionToFnInserction<T> extends () => infer R ? UnionToTuple<Exclude<T, R>, [R, ...A]> : A

Solution by ljq108 #18153

// your answers

/**
 * UnionToFunc<1 | 2> => ((arg: 1) => void | (arg: 2) => void)
 */
type UnionToFunc<T> = T extends unknown ? (arg: T) => void : never

/**
 * UnionToIntersection<1 | 2> = 1 & 2
 */
type UnionToIntersection<U> = UnionToFunc<U> extends (arg: infer Arg) => void
  ? Arg
  : never

/**
 * LastInUnion<1 | 2> = 2
 */
 type LastInUnion<U> = UnionToIntersection<UnionToFunc<U>> extends (x: infer L) => void
  ? L
  : never

type UnionToTuple<T, L = LastInUnion<T>> = [L] extends [never]
  ? []
  : [...UnionToTuple<Exclude<T, L>>, L]

Solution by humandetail #16449

// your answers
type UnionToIntersection<
    U> =
    (U extends any ? (arg: U)=>any : never) extends (arg: infer R)=>any ? R : never ;

type LastOfUnion<
    U> =
    UnionToIntersection<U extends any ? ()=>U : never> extends ()=>infer R ? R : never;

type UnionToTuple<
    U,
    Last extends any = LastOfUnion<U>> =
    [U] extends [never]
    ? []
    : [Last,...UnionToTuple<Exclude<U,Last>>]   

Solution by justBadProgrammer #15869

// your answers
type UnionToFn<T> = (
  T extends unknown ? (k:() => T) => void : never
  ) extends( (k: infer R) => void) ? R : never

type UnionToTuple<T, P extends any[] = []> = UnionToFn<T> extends () => infer R ? Exclude<T, R> extends never ? [...P, R] : UnionToTuple<Exclude<T, R>, [...P, R]> : never;

Solution by FeelyChau #13938

type getLastElm<T> = ((T extends T ? (a: (a: T) => void) => void : never) extends ((a: infer R) => void) ? R : never) extends (a: infer L) => void ? L : never

type UnionToTuple<T, Tuples extends unknown[] = []> = getLastElm<T> extends never ? Tuples : UnionToTuple<Exclude<T, getLastElm<T>>, [...Tuples, getLastElm<T>]>

Solution by a145789 #13928

type UnionToIntersectionFn<TUnion> = (
  TUnion extends TUnion ? (union: () => TUnion) => void : never
) extends (intersection: infer Intersection) => void
  ? Intersection
  : never;

type LastUnion<TUnion> = UnionToIntersectionFn<TUnion> extends () => infer Last
  ? Last
  : never;

type UnionToTuple<
  TUnion,
  TResult extends Array<unknown> = []
> = TUnion[] extends never[]
  ? TResult
  : UnionToTuple<
      Exclude<TUnion, LastUnion<TUnion>>,
      [...TResult, LastUnion<TUnion>]
    >;

Solution by michaltarasiuk #12602

// your answers

// 'a' | 'b' | 'c' => ()=>'a' & ()=>'b' & ()=>'c'
// 知识点:函数参数类型是逆变的
type UnionToIntersectionFn<U> = (U extends unknown ?
  (k: () => U) => void : 
  never) extends (k: infer I) => void ?
    I :
    never;

// ()=>'a' & ()=>'b' & ()=>'c' => 'c'
// 知识点1:函数交叉类型与函数重载本质上一样
// 知识点2: https://github.com/Microsoft/TypeScript/issues/24275#issuecomment-390701982
type GetLastReturnType<U> = UnionToIntersectionFn<U> extends ()=>infer R ?
  R :
  never;
  

type UnionToTuple<U, T extends Array<unknown> = []> = [U] extends [never] ?
  T :
  UnionToTuple<Exclude<U, GetLastReturnType<U>>, [...T, GetLastReturnType<U>]>;

Solution by Joyee691 #11541

type UnionToTuple<U, Result extends unknown[] = [], I extends U = U>
  = [U] extends [never] ? Result
  : [I extends I ? (_: Promise<I>) => 0 : 0] extends [(_: infer A) => 0] ? UnionToTuple<Exclude<U, Awaited<A>>, [...Result, Awaited<A>]>
  : never

Playground

Solution by teamchong #11473

type Union2IntersectionFn<T> = (
  T extends unknown ? (k:() => T) => void : never
  ) extends( (k: infer R) => void) ? R : never
type GetUnionLast<U> = Union2IntersectionFn<U> extends () => infer I 
  ? I : never;

type UnionToTuple<T, R extends any[] = []> = [T] extends [never] ? R : UnionToTuple<Exclude<T,GetUnionLast<T> >, [GetUnionLast<T>,...R]>

Solution by wqs576222103 #11061

// your answers
type UnionToIntersectionFn<U> = (
  U extends unknown ? (k: () => U) => void : never
) extends (k: infer I) => void ? I : never;

type GetUnionLast<U> = UnionToIntersectionFn<U> extends () => infer I 
  ? I : never;

type Prepend<Tuple extends unknown[], First> = [First, ...Tuple];

type UnionToTuple<
  Union, 
  T extends unknown[] = [], 
  Last = GetUnionLast<Union>
> = [Union] extends [never] 
  ? T 
  : UnionToTuple<Exclude<Union, Last>, Prepend<T, Last>>;

Solution by astak16 #10191

type UnionToIntersection<T> =( T extends T ? (params: T) => any : never) extends (params: infer P) => any ? P : never
type UnionToTuple<T, Res extends any[] = []> 
 = UnionToIntersection<T extends any ? () => T : never> extends ()=> infer ReturnType ? UnionToTuple<Exclude<T,ReturnType>,[...Res,ReturnType]>: Res

Solution by EGyoung #8549

/**
 * 从 Unoin 类型里面取出最后一个
 * 首先把传入的 Unoin 类型分配构造成函数 Unoin:
 * a | b ==> (() => a) | (() => b)
 * 然后丢进 UnoinToIntersection 处理成交叉类型
 * (() => a) & (() => b) 这个会被解释为函数重载
 * 然后再条件语句中,函数重载取最后一个值,所以拿到
 * 了传入 Unoin 的最后一个值
 */
type LastOf<T> = UnionToIntersection<
  T extends any ? () => T : never
> extends () => infer R
  ? R
  : never

// 这里是如何去掉 undefined | void | 1 中的undefined的呢
// 因为 undefined 可以赋值给 void,所以在
// Exclude<undefine, void> 的时候就会被干掉
type UnionToTuple<T, L = LastOf<T>, N = IsNever<T>> = true extends N
  ? []
  : Push<UnionToTuple<Exclude<T, L>>, L>

Solution by godlanbo #7551

// 730 - Union to Tuple

type UnionToIntersection = ( U extends unknown ? (arg: U) => 0 : never ) extends (arg: infer I) => 0 ? I : never;

type LastInUnion = UnionToIntersection< U extends unknown ? (x: U) => 0 : never

extends (x: infer L) => 0 ? L : never;

type UnionToTuple<U, Last = LastInUnion> = [U] extends [never] ? [] : [...UnionToTuple<Exclude<U, Last>>, Last];

Solution by Carefree-happy #7091

type OmitX<T, K> = T extends T
  ? T extends K
    ? never
    : T
  : never;

type UnionToTuple<T, K = T> = [T] extends [never]
  ? []
  : T extends T
    ? [T, ...UnionToTuple<OmitX<K, T>>]
    : []

Solution by Luncher #3968

// union to intersection of functions
type UnionToIoF<U> =
    (U extends any ? (k: (x: U) => void) => void : never) extends
    ((k: infer I) => void) ? I : never

// return last element from Union
type UnionPop<U> = UnionToIoF<U> extends { (a: infer A): void; } ? A : never;

// prepend an element to a tuple.
type Prepend<U, T extends any[]> =
    ((a: U, ...r: T) => void) extends (...r: infer R) => void ? R : never;

type UnionToTupleRecursively<Union, Result extends any[]> = {
    1: Result;
    0: UnionToTupleRecursively_<Union, UnionPop<Union>, Result>;
    // 0: UnionToTupleRecursively<Exclude<Union, UnionPop<Union>>, Prepend<UnionPop<Union>, Result>>
}[[Union] extends [never] ? 1 : 0];

type UnionToTupleRecursively_<Union, Element, Result extends any[]> =
    UnionToTupleRecursively<Exclude<Union, Element>, Prepend<Element, Result>>;

type UnionToTuple<U> = UnionToTupleRecursively<U, []>;

Solution by Jcanno #3112

// https://github.com/type-challenges/type-challenges/issues/737
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (
  x: infer U
) => any
  ? U
  : never

// get last Union: LastUnion<1|2> => 2
// ((x: A) => any) & ((x: B) => any) is overloaded function then Conditional types are inferred only from the last overload
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#type-inference-in-conditional-types
type LastUnion<T> = UnionToIntersection<
  T extends any ? (x: T) => any : never
> extends (x: infer L) => any
  ? L
  : never

type UnionToTuple<T, Last = LastUnion<T>> = [T] extends [never]
  ? []
  : [...UnionToTuple<Exclude<T, Last>>, Last]

Solution by myNameIsDu #2835

// your answers
// is incorrect!
// type UnionToTuple<T> = [T] extends [never] ? never : T extends undefined ? never : [T]

/**
 * UnionToIntersection<{ foo: string } | { bar: string }> =
 *  { foo: string } & { bar: string }.
 */
type UnionToIntersection<U> = (
  U extends unknown ? (arg: U) => 0 : never
) extends (arg: infer I) => 0
  ? I
  : never;

/**
 * LastInUnion<1 | 2> = 2.
 * 
 */
type LastInUnion<U> = UnionToIntersection<
  U extends unknown ? (x: U) => 0 : never
> extends (x: infer L) => 0
  ? L
  : never;

/**
 * UnionToTuple<1 | 2> = [1, 2].
 * see https://github.com/type-challenges/type-challenges/issues/737#issuecomment-791505468
 */
type UnionToTuple<U, Last = LastInUnion<U>> = [U] extends [never]
  ? []
  : [...UnionToTuple<Exclude<U, Last>>, Last];
type h = ((x: 1) => 0) & ((x: 2) => 0) //why h not never

Function arguments are in contravariant positions, so when functions intersect, arguments do not intersect, but are united. This intersection of functions forms an overload -- a function that takes either 1 or 2 as its first argument.

type e = (((x: 1) => 0) & ((x: 2) => 0)) extends (x: infer L) => 0 ? L : never; //  why e is 2 not never or 1?

This is a feature of TS, mentioned somewhere in the documentation -- if it is necessary to output one type from overload, TS selects the last signature ((x: 2) => 0) in the overload.

Solution by zongzi531 #2161