Search code examples
typescriptconditional-types

Using conditional type on an optional argument


I'm defining from function that can take 1 or 2 arguments; it returns a lambda that takes the same number of arguments.

I thought the return type of this function could be a conditional type verifying if the second argument is set, and returning a lambda with 1 or 2 arguments as appropriate:

type Consumer1<V1> = (a1: V1) => void
type Consumer2<V1, V2> = (a1: V1, a2: V2) => void

class Wrapper<V> {
    unwrap(a: V) {}
}

function from<V1, V2>(a1: Wrapper<V1>, a2?: Wrapper<V2>):
  typeof a2 extends undefined ? Consumer1<V1> : Consumer2<V1, V2>
{
    if (typeof a2 === 'undefined') {
        return (a1: V1) => {}
    }
    return (a1: V1, a2: V2) => {}
}


from(new Wrapper<number>())(1)
// expected 2 arguments, but got one

(Playground link.)

As one can see on the last line of the block above, from always returns a Consumer2. Any idea as of why? I thought that since a2 is not set, typeof a2 would be undefined and from would return a Consumer<number>.

Thanks, JM


Solution

  • The most straightforward solution is probably to use function overloads:

    type Consumer1<V1> = (a1: V1) => void
    type Consumer2<V1, V2> = (a1: V1, a2: V2) => void
    
    class Wrapper<V> {
        unwrap(a: V) {}
    }
    
    function from<V1>(a1: Wrapper<V1>): Consumer1<V1>;
    function from<V1, V2>(a1: Wrapper<V1>, a2: Wrapper<V2>): Consumer2<V1, V2>;
    function from<V1, V2>(a1: Wrapper<V1>, a2?: Wrapper<V2>): 
        Consumer1<V1> | Consumer2<V1, V2> {
        
        if (typeof a2 === 'undefined') {
            return (a1: V1) => {}
        }
        return (a1: V1, a2: V2) => {}
    }
    
    from(new Wrapper<number>())(1);
    

    Playground link