Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to replace return type of function type #39594

Open
5 tasks done
justinfagnani opened this issue Jul 14, 2020 · 5 comments
Open
5 tasks done

Ability to replace return type of function type #39594

justinfagnani opened this issue Jul 14, 2020 · 5 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@justinfagnani
Copy link

Search Terms

  • replace return type
  • parameters generic

Suggestion

I would like to be able to replace just the return type of a function with a utility type, like WithReturn<F, R>.

I'm not 100% sure this is needed, but it seems like using (...args: Parameters<F>) => R is not sufficient because any generics in F are lost.

Use Cases

My use case is a utility function that captures the arguments to a function to allow it to be called later.

Examples

export const defer = <F extends (...args: any) => any>(f: F) => ((
  ...args: Parameters<F>
) => ({
  function: f,
  args,
})) /* as any as F */;

const map = <T, R>(array: Array<T>, f: (i: T) => R) => array.map(f);
const deferredMap = defer(map);

let r = deferredMap([1], (i: number) => 2); // error now: Type 'unknown' is not assignable to type 'number'.(2345)
r.args; // error if defer returns F

Right now there's an error because the generics to map() are lost when using Parameters.

Playground: https://www.typescriptlang.org/play/?ssl=13&ssc=1&pln=1&pc=1#code/KYDwDg9gTgLgBAYwgOwM7wCbAGbCnAXjgB4AxOUGYZDVOACgDpmBDKAc1QC44XkBPAJSEAfLwEj62HqWEEx9egCg4cZozaceABTYsAtsCpRUZEUrkKA3irjYArsgQwAlih7YANLc2pvAX0FhAHoAKl46Pn4IuHJQ4IBuJSUkNHh9FjBCEgAVTzgAJUk2KBZ+HgBBKFL+YhyRfOkGFx4cy0L2krLGDLApQSTU9DgsXGrgDABZTOzRvHpegeSAGyM4fCI58anM+gBtAEYAXXz6Frhke30AIzx2gCYlqA0OVCSgA

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jul 15, 2020
@awerlogus
Copy link

Very useful feature. This is the only way to implement flow function with unlimited parameters count, for example. We just need to recursively compute return type of the functions chain passed (this can be done using #38182) and then replace return type of the first function with it. There're very much cases of functions wrapping other functions and changing its return types. @RyanCavanaugh, You really should pay attention to this proposal.

@TylorS
Copy link

TylorS commented Aug 22, 2020

This would indeed be a very useful feature! I think it might be incredibly useful to have a syntax for mapping over the entirety of a function type including type parameters and return type, similar to a mapping over object/tuple types. Does that sound useful to others in the thread?

@awerlogus
Copy link

@TylorS We need to look at the code examples

@saltman424
Copy link

saltman424 commented Nov 15, 2024

Just to add some life to this four-year-old issue. I have a use case of factories of factories. For example:

Playground

interface CacheEntry<T> {
  value: T
  cachedAt: Date
}

// This is where I want to swap the return type
export function createCachedFactory<TFn extends (...args: any) => any>(factory: TFn) {
  const cache: Record<string, CacheEntry<ReturnType<TFn>>> = {}
  return (...args: Parameters<TFn>) => {
    const cacheKey = JSON.stringify(args)
    return cache[cacheKey] ??= { value: factory(...args), cachedAt: new Date() }
  }
}

interface Foo1 { bar1: number }
export const makeFoo1 = createCachedFactory((bar1: number): Foo1 => ({ bar1 }))

interface Foo2<T> { bar2: T }
// This should be typed as: <T>(bar2: T) => CacheEntry<Foo2<T>>
export const makeFoo2 = createCachedFactory(<T>(bar2: T): Foo2<T> => ({ bar2 }))

Here are some proposed solutions (in order of desirability, with most desirable at the top):

// Proposal 1: TypeScript intelligently determines that the inferred parameters
// are used in the new parameters and the inferred return type is used in the
// new return type, so the new function type should be generic, if the original
// function type is generic
declare function createCachedFactory<TFn extends (...args: any) => any>(factory: TFn):
  TFn extends (...args: infer TParams) => infer TRet 
    ? (...args: TParams) => CacheEntry<TRet>
    : (...args: Parameters<TFn>) => CacheEntry<ReturnType<TFn>>

// Proposal 2: similar to proposal 1, but TypeScript is using the utility types
// Parameters and ReturnType instead of directly inferred types
declare function createCachedFactory<TFn extends (...args: any) => any>(factory: TFn):
  (...args: Parameters<TFn>) => CacheEntry<ReturnType<TFn>>

// Proposal 3: a special type is used to add handling of generic functions. I am
// not sure if this is really much easier to implement than proposal 1 or 2 as
// there still needs to be some guardrails to handle misusing or not
// inferring/using TParams and TRet
declare function createCachedFactory<TFn extends (...args: any) => any>(factory: TFn):
  TFn extends GenericFunction<infer TParams, infer TRet>
    ? (...args: TParams) => CacheEntry<TRet>
    : (...args: Parameters<TFn>) => CacheEntry<ReturnType<TFn>>

// Proposal 4: similar to proposal 3, but with additional special types for the
// parameters and return types
declare function createCachedFactory<TFn extends (...args: any) => any>(factory: TFn):
  TFn extends GenericFunction
    ? (...args: GenericParameters<TFn>) => CacheEntry<GenericReturnType<TFn>>
    : (...args: Parameters<TFn>) => CacheEntry<ReturnType<TFn>>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants