tipo utilitario de TypeScript que puede cambiar el tipo de retorno de todas las firmas/sobrecargas de funciones a un nuevo tipo
Tengo un tipo en una biblioteca que quiero modificar cambiando el tipo de retorno de cada firma de función en este tipo.
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
}
Quiero crear un tipo utilitario que pueda convertir todas las firmas de función para tener un tipo de retorno diferente.
// 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
// }
Necesito ayuda para obtener el RemapFunctionReturnType recursivo para todas las firmas. Esto es lo que he intentado hasta ahora, pero solo funciona para una sola firma:
type RemapFunctionReturnType<T, U> = T extends (...args: infer P) => any & infer Next ? (...args: P) => U | RemapFunctionReturnType<Next, U> : never;
Respuesta #1
Actualmente es una limitación de TypeScript que las sobrecargas no puedan ser manipuladas fácilmente de manera programática en el sistema de tipos. Cuando se llama directamente a una función sobrecargada, el compilador resolverá la llamada con la firma de llamada apropiada de la lista dependiendo de los argumentos.
Pero cuando intentas usar genéricos o tipos condicionales para sondear funciones sobrecargadas en el sistema de tipos, invariablemente aparecerán como si tuvieran solo una única firma de llamada (usualmente la última en la lista). Hay una solicitud abierta en microsoft/TypeScript#29732 para que los tipos condicionales como tu RemapFunctionReturnType devuelvan algo mejor, pero por ahora no forma parte del lenguaje.
Los métodos alternativos que conozco para convertir un tipo sobrecargado en una lista ordenada de sus firmas de llamada constituyentes solo admiten hasta un número fijo y arbitrario de firmas de llamada. Véase Parameters genérico de función sobrecargada no contiene todas las opciones y Typescript: ReturnType de función sobrecargada. Lo siguiente funciona más o menos para hasta cuatro sobrecargas:
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
Y ves que esta definición escala en tamaño con el cuadrado del número de sobrecargas que deseas poder soportar. Armado con un Overloads lo suficientemente grande, podrías escribir estos:
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 };
Lo que te daría el tipo que estás buscando (una intersección de funciones en TypeScript es equivalente a una función sobrecargada):
type ModifiedChainer = FromOverloads<ModifyReturn<Overloads<Chainer>, number>>
/* type ModifiedChainer =
((chainer: "be.a", type: string) => number) &
((chainer: "be.above", value: number | Date) => number)
*/
Eso es genial, pero si solo estás escribiendo Overloads<T> para generar ModifiedChainer, entonces necesariamente estarás haciendo mucho trabajo manual extra para evitar una cantidad media de trabajo manual extra. Así que casi con seguridad no vale la pena.
Hasta que y a menos que algo como microsoft/TypeScript#29732 se implemente, bien podrías simplemente encontrar la definición de Chainer y hacer una búsqueda y reemplazo manual en un IDE, y simplemente recordarte a ti mismo revisar los cambios ascendentes en el tipo Chainer original.
Enlace de Playground al código
