Henning Hall
Henning Hall

Reputation: 1457

Omit return type from a function type?

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

Answers (2)

Gerrit Begher
Gerrit Begher

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

jcalz
jcalz

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

Playground link to code

Upvotes: 3

Related Questions