Reputation: 1457
Is it possible to create a new function type from an existing typescript function that has the same parameters and generics but without the return type?
Example:
I am trying to create the type ReturnNothing
from the ReturnSame
function without having to define it manually.
type ReturnSame = <T extends string>(a: T) => T
type ReturnNothing = <T extends string>(a: T) => void
Attempts:
function returnSame<T extends string>(a: T): T {
return a;
}
// Attempt 1: Does not work. Generics is not following to new type.
const returnNothing1 = (...params: Parameters<typeof returnSame>) => {
console.log(params);
};
// Attempt 2: Does not work. Return type is forced.
const returnNothing2: typeof returnSame = (...params) => {
console.log(params);
};
// Attempt 3: Does not work. OmitReturn is a made up Typescript utility.
const returnNothing3: OmitReturn<typeof returnSame> = (...params) => {
console.log(params);
};
// Usage
const same = returnSame<"a">("a");
const nothing1 = returnNothing1<"b">("b");
Upvotes: 1
Views: 1179
Reputation: 403
In addition to what @jcalz said: It's partially possible at the type level. (Playground link)
type GenFN<S, T> = <AS extends S>(a: AS) => T
type VoidFN<G extends GenFN<any, any>> =
G extends GenFN<infer S, any>
? GenFN<S, void>
: never
type ThisWorks = VoidFN<GenFN<1, 2>>
type ThisDoesNotWork = VoidFN<<AS extends 1>(a: AS) => 2>
But this would require you to use the helper GenFN
everywhere instead of direct definitions like <AS extends 1>(a: AS) => 2
, rendering VoidFN
incompatible with parts of your (current and future) codebase and third party libraries.
Another risk: I am not sure if TS could maybe "forget"/"discard" the information that a specific type extends GenFN
due to internal optimiziations should your codebase become large and the type definitions deeply nested.
Upvotes: 0
Reputation: 327754
There's currently no way to do this automatically purely at the type level. Meaning: you can't take a generic function type like ReturnSame
and grab its parameters in a way that doesn't immediately plug in unknown
(or whatever its generic constraint is) for its type parameter(s) like T
. Doing this would probably require higher kinded types which TypeScript does not currently support directly (see microsoft/TypeScript#1213 for the feature request).
You could give up completely and write out returnNothing()
manually as a generic function, but you don't want to do that.
If it's acceptable to drop down to the value level, by passing returnSame()
to a function, you can leverage higher order type inference from generic functions as introduced in TypeScript 3.4:
function makeNothingReturner<A extends any[]>(fn: (...a: A) => any): (...a: A) => void {
return (...a) => void fn(...a);
}
const returnNothing = makeNothingReturner(returnSame);
Here makeNothingReturner()
takes a (possibly generic) function and returns another function with the same parameters with a void
return type. The type signature for this looks very much like what you were doing in your type-level code, so it might be surprising that this would not do the same thing and have returnNothing
be of type (...args: unknown) => void
. But behold, IntelliSense shows this:
// const returnNothing: <T extends string>(a: T) => void
and thus you can use it as desired, by calling with a specified type parameter:
const nothing = returnNothing<"b">("b"); // works
Upvotes: 3