0xf

日記だよ

久しぶりに typescript 書いてて型にハマるなどしていた

function wrapper<T extends (...args: any) => { Status: number, Result: any }>(f: T): T {
    return f
}

これを書き換えて、

function wrapper<T extends (...args: any) => { Status: number, Result: any }>(f: T, ...params: Parameters<T>): ReturnType<T> {
    return f(...params) // Type '{ Status: number; Result: any; }' is not assignable to type 'ReturnType<T>' となる
}

// あるいは以下のようなコードでも同じ
function wrapper<T extends (...args: any) => { Status: number, Result: any }>(f: T) {
    return function (...params: Parameters<T>): ReturnType<T> {
        return f(...params)
    }
}

こうしたかったのだけどコンパイルが通らない。Tの戻り値型の制約を除くと通るのね。こう。

function wrapper<T extends (...args: any) => any>(f: T, ...params: Parameters<T>): ReturnType<T> {
    return f(...params)
}

シグネチャ側に関数戻り値として ReturlType<T> を指定しているけど、これを外すと{ Status: number, Result: any } に戻り値が推論されてしまう。関数呼び出し時に戻り値が { Status: number, Result: string } の関数を渡しても同様で、あくまでも利用時に設定された関数の戻り値の型を採用したいわけです。型パラメータの指定は制約にとどまっていてほしい。

というか、fT なのだから、 ReturnType<T> に対して f() の結果は代入可能でしょう。

いっそ以下のように倒してしまった方が潔いんじゃないのか。

function wrapper<T extends (...args: any) => { Status: number, Result: any }>(f: T, ...params: Parameters<T>): ReturnType<T> {
    return f(...params) as any // f は Tなのだから ReturnType<T> に絶対合致する
}

as any を使って良いのは typescript コンパイラより自分の方が賢いと確信できるときだけにしなさいと会社の人も書いてたが、まあこういう場合はしょうがないと思っている。

--

追記:

なんでそうなるのかは納得しました。

as any ではなく as ReturnType<T> にするのが妥当そう。