Repository: TypeScript
Issue: Introduce mechanism for manipulating function types without losing generic type information
Labels: Suggestion,Awaiting More Feedback

# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## 🔍 Search Terms

<!--
  💡 Did you know? TypeScript has over 2,000 open suggestions!
  🔎 Please search thoroughly before logging new feature requests as most common ideas already have a proposal in progress.
  The "Common Feature Requests" section of the FAQ lists many popular requests: https://github.com/Microsoft/TypeScript/wiki/FAQ#common-feature-requests

  Replace the text below:
-->

- generic function add parameter
- parameters of generic function
- prepend parameter to generic function

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

<!-- A summary of what you'd like to see added or changed -->

Being able to manipulate generic function types without losing the generic type parameters would be very useful (see under motivating example). Coming up with a good syntax for it is not easy, but I see a few possible approaches (precise syntax/names subject to change):

1. A bunch of utility types (inelegant but gets the job done)
```ts
type F = <T, R>(t: T) => R;

type X1 = WithReturnType<F, number>; // X1 = <T, R>(t: T) => number;
type X2 = WithParameters<F, [x: number]>; // X1 = <T, R>(x: number) => R;
type X3 = PrependParameter<F, number>; // X1 = <T, R>(arg0: number, t: T) => R;
type X4 = AppendParameter<F, number>; // X1 = <T, R>(t: T, arg0: number) => R;
type X5 = ReplaceParameter<F, 0, number>; // X1 = <T, R>(t: number) => R;
```

2. Enhancement of `extends` as is (probably the prettiest solution but may present challenges when speccing it out):
```ts
type F = <T, R>(t: T) => R;

type X1 = F extends (...args: infer Args) => infer R
    ? (x: number, ...args: Args) => R
    : never;
// ...
```

3. Enhancement of `extends` with new syntax to declare intent (this is much more general but would only make sense when combined with a broader "generic generics" or HKT feature):
```ts
type F = <T, R>(t: T) => R;

type X1 = F extends <...infer TypeArgs>(...args: infer Args) => infer R
    ? (x: number, ...args: Args<...TypeArgs>) => R<...TypeArgs>
    : never;
// ...

// or maybe
type X1 = F extends <...infer TypeArgs>(...args: infer Args<...TypeArgs>) => infer R<...TypeArgs>
    ? (x: number, ...args: Args<...TypeArgs>) => R<...TypeArgs>
    : never;
// ...
```

4. Alternative extension to existing syntax (in general, but here's one possibility):
```ts
type F = <T, R>(t: T) => R;

type X1 = <...Args>(x: number, ...args: Parameters<F<...Args>>) => ReturnType<F<...Args>>;
```

## 📃 Motivating Example

<!--
  If you were announcing this feature in a blog post, what's a short explanation that shows
  a developer why this feature improves the language?
-->

Consider some kind of API/middleware builder that looks like this when used:

```ts
builder
    .add('slow', { rate: 500 })
    .add('get', { query: ['abc'] as const }, ({ query: { abc } }) => {
        // ...
    };
```

It could be typed as something like this:

```ts
interface Builder {
    add(type: 'slow', options: { rate: number }): Builder;
    add<Q extends readonly string[]>(
        type: 'get',
        options: { query: Q },
        callback: (data: { query: { [K in Q[number]]: string } }) => void
    ): Builder;
    // ...
}
```

However, now we want third parties to be able to add their own middleware types to the builder. Making an API for adding them is fairly easy:

```ts
BuilderFactory.register('merge', (options, callback) => {
    // ...
    callback(...);
    // ...
} );
```

But typing it is much harder. Usually we would use module augmentation to allow third parties to type their middleware types:

```ts
interface Builder {
    add<T extends keyof BuilderMethods>(type: T, ...args: Parameters<BuilderMethods[T]>): ReturnType<BuilderMethods[T]>
}

// third party code
declare module '...' {
    interface BuilderMethods {
        merge: <Q extends readonly string[]>(
            options: { query: Q },
            callback: (data: { query: { [K in Q[number]]: string } }) => void
        ) => void;
    }
}
```

But if we do this, we lose all of the information encoded in the type parameters. This feature would allow the `add` method to retain the generic type information of the function type it's derived from.

## 💻 Use Cases

My use case would be relatively similar to the motivating example above. Currently my workaround is to just only have a good typing experience for first-party methods, but that's not ideal.