Search code examples
typescriptgenericstypescurrying

ReturnType of curried function


Using ReturnType<T> in a function returns, as you would expect, the return type.

type F = () => string
//ReturnType<F> === string

Also, as you would expect, when using curried functions the return type is

type FF = () => () => string
//ReturnType<FF> === () => string

Is there a way to create a generic type that returns the final return? I.E

type FF = () => () => string
//FinalReturnType<FF> === string

Solution

  • This currently works (TS 3.7), but the TS team didn’t intentionally allow it and hasn’t decided to support it. It may break in future versions.

    type FinalReturnType<T> = {
      0: T,
      1: T extends (...args: any) => infer R ? FinalReturnType<R> : T,
    }[T extends (...args: any) => infer _ ? 1 : 0];
    

    It has to be this strange and ugly because there's currently a restriction that says a type can only call itself if it's nested inside another structural type (in this case, an object with two keys), but then we immediately pull the property we need from the tuple.

    How it would ideally look:

    If the language supported this kind of mechanism, I'd think it would be as simple as:

    type FinalReturnType<T> = T extends (...args: any) => infer R
                                  ? FinalReturnType<R> 
                                  : T;
    

    In other words, use a recursive definition. If T is a function, it calls itself passing the return type of the function. If not a function, it returns T itself. So for example:

    FinalReturnType<string> // -> string
    

    And so that terminates the recursion: when you've unwrapped all the layers of functions that return functions, you eventually reach a non-function.

    Unfortunately, this isn't possible, producing the error:

    Type alias 'FinalReturnType' circularly references itself.
    

    There was a change to enable recursive type references but it doesn't enable this scenario.

    There is an open discussion of more general support for recursion.

    The present situation is that you can only compile my answer if you add [] around the self-call to make it a tuple:

    type FinalReturnType<T> = T extends (...args: any) => infer R
                                    ? [FinalReturnType<R>] 
        : T;
    
    type t1 = FinalReturnType<string>;  // -> string
    
    type F = () => string
    type t2 = FinalReturnType<F>;       // -> [string]
    
    type FF = () => () => string
    type t3 = FinalReturnType<FF>;      // -> [[string]]
    

    As you can see, this re-wraps the results in layers of nested tuples, and you're back to the same problem, because you can't unwrap those either.

    Currently you can currently use the trick of wrapping in an object with two properties, then immediately unwrapping by indexing the right one, as described at the top of this answer. But this was enabled by accident, and the TS team aren’t sure whether they want to support/encourage this.