简体   繁体   中英

typescript util type that can change the return type of all function signatures/overloads to a new type

I have a type in a library that I want to modify by changing the return type of every function signature in this type.

interface Chainer<Subject> {  
  (chainer: 'be.a', type: string): Cypress.Chainable<Subject>
  (chainer: 'be.above', value: number | Date): Cypress.Chainable<Subject>
  // ... many many more overloads here
}

I want to create a util type that can convert all the function signatures to have a different return type.

// This is the util that should change the return type
type RemapFunctionReturnType<T, U> = T extends (...args: infer P) => any ? (...args: P) => U : never;

type ModifiedChainer = RemapFunctionReturnType<Chainer<any>, number>;

// All return types changed to number
// interface ModifiedChainer<Subject> {  
//   (chainer: 'be.a', type: string): number; 
//   (chainer: 'be.above', value: number | Date): number;
//   // ... many many more overloads here
// }

Need help with getting the RemapFunctionReturnType recrusive for all signatures. This is what I have tried so far but it only works for a single signature:

type RemapFunctionReturnType<T, U> = T extends (...args: infer P) => any & infer Next ? (...args: P) => U | RemapFunctionReturnType<Next, U> : never;

It is currently a limitation of TypeScript that overloads cannot easily be programmatically manipulated in the type system. When you directly call an overloaded function, the compiler will resolve the call with the appropriate call signature from the list depending on the arguments.

But when you try to use generics or conditional types to probe overloaded functions in the type system, they will invariably appear as if they had just a single call signature (usually the last one on the list). There's an open request at microsoft/TypeScript#29732 for conditional types like your RemapFunctionReturnType to return something better, but for now it's not part of the language.


The workarounds I know of to tease an overloaded type into an ordered list of its constituent call signatures only support up to some arbitrary fixed number of call signatures. See Parameters generic of overloaded function doesn't contain all options and Typescript: ReturnType of overloaded function . The following thing works-ish for up to four overloads:

type Overloads<T> =
  T extends {
    (...args: infer A1): infer R1; (...args: infer A2): infer R2;
    (...args: infer A3): infer R3; (...args: infer A4): infer R4
  } ?
  [(...args: A1) => R1, (...args: A2) => R2, (...args: A3) => R3, (...args: A4) => R4] :
  T extends {
    (...args: infer A1): infer R1; (...args: infer A2): infer R2;
    (...args: infer A3): infer R3
  } ?
  [(...args: A1) => R1, (...args: A2) => R2, (...args: A3) => R3] :
  T extends {
    (...args: infer A1): infer R1; (...args: infer A2): infer R2
  } ?
  [(...args: A1) => R1, (...args: A2) => R2] :
  T extends {
    (...args: infer A1): infer R1
  } ?
  [(...args: A1) => R1] :
  any

And you see that this definition scales in size with the square of the number of overloads you want to be able to support. Armed with a "large enough" Overloads helper, you could write these:

type FromOverloads<T extends readonly any[]> =
    { [K in keyof T]: (x: T[K]) => void }[number] extends 
    (x: infer U) => void ? U : never;

type ModifyReturn<T extends readonly ((...args: any) => any)[], U> =
    { [K in keyof T]: T[K] extends (...args: infer A) => void ?   
    (...args: A) => U : never };

Which would give you the type you're looking for (an intersection of functions in TypeScript is equivalent to an overloaded function):

type ModifiedChainer = FromOverloads<ModifyReturn<Overloads<Chainer>, number>>
/* type ModifiedChainer = 
     ((chainer: "be.a", type: string) => number) & 
     ((chainer: "be.above", value: number | Date) => number) 
*/

That's great, but if you're only writing Overloads<T> in order to generate ModifiedChainer , then you'll necessarily be doing a lot of extra manual work to avoid a medium amount of extra manual work. So it's almost certainly not worth it.

Until and unless something like microsoft/TypeScript#29732 is implemented, you might as well just find the definition of Chainer and do a manual find-and-replace in an IDE, and just remind yourself to check for upstream changes to the original Chainer type.

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM