toriningen
toriningen

Reputation: 7462

Specialize a type for generic function

Given this definition:

declare function foo<T>(): { bar: T }

// <T>() => { bar: T }
type Foo = typeof foo;

How can one provide specialization to the generic function by its type?

What I want to achieve is to be able to do something like this:

// { bar: number }
type FooResult = ReturnType<Foo<number>>;

But TypeScript complaints that Foo itself is not generic - the function it types is.

Upvotes: 2

Views: 697

Answers (1)

jcalz
jcalz

Reputation: 327624

TypeScript doesn't really support the kind of higher-order typing you'd need to get this from ReturnType... it's a known design limitation. So all you have available is a variety of workarounds. Here are the ones I can think of off the top of my head:

  • Do it manually. This is essentially a non-answer, but could be the best way forward since it doesn't rely on any weird type system tricks:

    type FooResult<T> = { bar: T };
    type FooResultNumber = FooResult<number>; // {bar: number}
    
  • Pretend to actually call foo() and get its result. TypeScript doesn't support arbitrary type queries, so type FooResult = typeof foo<number>() unfortunately does not compile. The following code is about as close as you can get:

    const __dummy = (true as false) || foo<number>();
    type FooResultNumber = typeof __dummy; // {bar: number}
    

    This introduces a dummy variable in your runtime code. The (true as false) || expression construct uses a type assertion to lie to the compiler. The compiler thinks you are doing false || expression, the type of which will be the same as the type of expression. What you are really doing at runtime is true || expression which short-circuits, returning true without ever evaluating expression. This means that foo() never gets called at runtime despite being in the code.

  • Another way to pretend to call foo() is with a dummy class... you will never instantiate the class but it lets the compiler reason about types:

    class __FooRunner<T> {
      result = foo<T>();
    }
    type FooResult<T> = __FooRunner<T>["result"];
    type FooResultNumber = FooResult<number>; // {bar: number}
    

    Again, this puts some junk in your runtime code, which may or may not be acceptable depending on your use cases.

Okay, hope that helps; good luck!

Link to code

Upvotes: 1

Related Questions