Type inference with overloaded functions

前端 未结 1 2006
盖世英雄少女心
盖世英雄少女心 2021-01-12 23:24

Consider the following overloaded function:

function foo(arg1: string, cb: (err: Error|null, res: string) => void): void
function foo(arg1: string, arg2:          


        
相关标签:
1条回答
  • 2021-01-13 00:15

    We can use the 3.0 feature of Tuples in rest parameters and spread expressions to get a union of the overload parameters but we need to add a case for each number of overloads the function has:

    export type GetOverloadArgs<T> = 
        T extends { (...o: infer U) : void, (...o: infer U2) : void, (...o: infer U3) : void   } ? U | U2 | U3:
        T extends { (...o: infer U) : void, (...o: infer U2) : void  } ? U | U2 :
        T extends { (...o: infer U) : void } ? U : never
    

    So for example for foo

    type fooParams = GetOverloadArgs<typeof foo> 
    // will be 
    type fooParams = [string, (err: Error | null, res: string) => void] | [string, string, (err: Error | null, res: string) => void]
    

    From this we can use a type similar to your Promisify to create a function for each parameter set in the union :

    export type PromisifyOne<T extends any[]> =
        T extends [Callback<infer U>?] ? () => Promise<U> :
        T extends [infer T1, Callback<infer P>] ? (arg1: T1) => Promise<P> :
        T extends [infer T1, infer T2, Callback<infer U>?] ? (arg1: T1, arg2: T2) => Promise<U> :
        T extends [infer T1, infer T2, infer T3, Callback<infer U>?]? (arg1: T1, arg2: T2, arg3: T3) => Promise<U> :
        T extends [infer T1, infer T2, infer T3, infer T4, Callback<infer U>?] ? (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<U> :
        T;
    

    And using the distributive behavior of conditional types we can create a union of all the overloads:

    export type Promisify<T> =PromisifyOne<GetOverloadArgs<T>> 
    export type Promisify<T> =PromisifyOne<GetOverloadArgs<T>> 
    type fooOverloadUnion = Promisify<typeof foo>
    // Same as 
    type fooOverloadUnion = ((arg1: string) => Promise<string>) | ((arg1: string, arg2: string) => Promise<string>)
    

    To make this callable again we ca use transform the union to an intersection, using UnionToIntersection, with the final result being:

    export type Callback<T> = (err: Error | null, reply: T) => void;
    export type PromisifyOne<T extends any[]> =
        T extends [Callback<infer U>?] ? () => Promise<U> :
        T extends [infer T1, Callback<infer P>?] ? (arg1: T1) => Promise<P> :
        T extends [infer T1, infer T2, Callback<infer U>?] ? (arg1: T1, arg2: T2) => Promise<U> :
        T extends [infer T1, infer T2, infer T3, Callback<infer U>?]? (arg1: T1, arg2: T2, arg3: T3) => Promise<U> :
        T extends [infer T1, infer T2, infer T3, infer T4, Callback<infer U>?] ? (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<U> :
        T;
    
    export type GetOverloadArgs<T> = 
        T extends { (...o: infer U) : void, (...o: infer U2) : void, (...o: infer U3) : void   } ? U | U2 | U3:
        T extends { (...o: infer U) : void, (...o: infer U2) : void  } ? U | U2 :
        T extends { (...o: infer U) : void } ? U : never
    
    type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
    export type Promisify<T> =  UnionToIntersection<PromisifyOne<GetOverloadArgs<T>>>
    
    // Sample
    declare function foo(arg1: string, cb: (err: Error|null, res: string) => void): void
    declare function foo(arg1: string, arg2: string, cb: (err: Error|null, res: string) => void): void
    
    declare const  fooPromise: Promisify<typeof foo>
    let r = fooPromise("")
    let r2 = fooPromise("", "")
    
    0 讨论(0)
提交回复
热议问题