TypeScript: ジェネリックな引数をとる関数の ReturnType を取得する

TypeScript: ジェネリックな引数をとる関数の ReturnType を取得する

February 23, 2022

ジェネリック型を引数にとる関数の戻り値の型を取得すると、結果は以下のようになります。

function foo<T>(arg: T) {
  return arg;
}

function bar<T extends object>(arg: T) {
  return arg;
}

type FooReturn = ReturnType<typeof foo>; // unknown

type BarReturn = ReturnType<typeof bar>; // object

実際には渡される引数によってさらに具体的な型になるわけですが、このように戻り値の型を定義しておこうとすると、ジェネリックな部分についてはどうしても型に反映できなくなります。

これで困るケースが以下のような場合です。

function foo<T>(arg: T) {
  return arg;
}

type FooReturn = ReturnType<typeof foo>;

function bar(arg: FooReturn) {
  return arg;
}

type BarReturn = ReturnType<typeof bar>;

function baz<T>(arg: T, callback: (arg: FooReturn) => BarReturn) {
  const fooReturn = foo(arg);
  return callback(fooReturn);
}

const obj = { name: 'alice' } as const;

const result = baz(obj, bar); // unknown

result の型は { name: 'alice' } となっていて欲しいものの BarReturn が unknown 型であることから result も unknown 型となってしまいます。そして BarReturn の型は FooReturn によって決定されているため、根本的な問題点は FooReturn が unknown 型となってしまっている点です。

result の型を { name: 'alice' } とするためには FooReturn 型をジェネリック型に対応させる必要がありますが、これは以下のようにすると実現できます。

function foo<T>(arg: T) {
  return arg;
}

class FooReturnWrapper<T> {
  func = (arg: T) => foo(arg);
}

type FooReturn<T> = ReturnType<FooReturnWrapper<T>['func']>;

function bar<T>(arg: FooReturn<T>) {
  return arg;
}

class BarReturnWrapper<T> {
  func = (arg: T) => bar(arg);
}

type BarReturn<T> = ReturnType<BarReturnWrapper<T>['func']>;

function baz<T>(arg: T, callback: <T>(arg: FooReturn<T>) => BarReturn<T>) {
  const fooReturn = foo(arg);
  return callback(fooReturn);
}

const obj = { name: 'alice' } as const;

const result = baz(obj, bar); // name: "alice";

このようにすると result の型が { name: 'alice' } となります。

実際の使用例を想定してコードを書きましたが、その結果長くなってしまいましたので改めてエッセンス部分だけを以下に抜き出します。

function foo<T>(arg: T) {
  return arg;
}

class FooReturnWrapper<T> {
  func = (arg: T) => foo(arg);
}

type FooReturn<T> = ReturnType<FooReturnWrapper<T>['func']>;

FYI #

以下に本件に関する機能改善の ISSUE もあがっていますが、いまのところ対応される予定はなさそうです。

Allow binding generic functions to a given type · Issue #37181 · microsoft/TypeScript