Repository: TypeScript
Issue: Function parameters and return type inference with infer keyword
Labels: Suggestion,In Discussion

I saw a couple of similar issues, but it all provided very poor specification of the problem, so I decided to open my own issue.

# Description
The further step in bringing nice fp declarations into TypeScript is adding function parameters and return type inference through `infer` keyword.

Lets look at some examples:

```typescript
type F1 = <T extends number>(a: T, b: T) => T

// Now: [number, number]
// Propose: [1, 1]
type R1 = F1 extends ((a: 1, b: infer P) => infer R) ? [P, R] : never
```

Here we passed parameter `a` as `1` and expect:
1. Type parameter `T` to be inferred as `1`
2. Type `P` to be inferred as `T` -> `1`
3. Type `R` to be inferred as `T` -> `1`  
4. Result to be inferred as `[P, R]` -> `[1, 1]` 

---

```typescript
type F2 = <T1 extends AnyTuple, T2>(a: T1, b: T2, c: Reverse<T1>) => [T1, T2]  

// Now: [never, unknown, [AnyTuple, unknown]]
// Propose: [[3, 2, 1], unknown, [[1, 2, 3], unknown]]
type R2 = F2 extends (a: [1, 2, 3], b: infer P2, c: infer P1) => infer R ? [P1, P2, R] : never
```

Here we passed parameter `a` as `[1, 2, 3]` and expect:
1. Type parameter `T1` to be inferred as `[1, 2, 3]`
2. Type parameter `T2` to be replaced with `unknown` type because we provided no info to be able to infer it 
3. Type `P2` to be inferred as `T2` -> `unknown` 
4. Type `P1` to be inferred as `Reverse<T1>` -> `Reverse<[1, 2, 3]>` -> `[3, 2, 1]`
5. Type `R` to be inferred as `[T1, T2]` -> `[[1, 2, 3], unknown]`
6. Result to be inferred as `[P1, P2, R]` -> `[[3, 2, 1], unknown, [[1, 2, 3], unknown]]

---

```typescript
type F3 = <T extends AnyTuple>(a: T, b: T['length']) => typeof b

// Now: never
// Propose: 3
type R3 = F3 extends ((a: [1, 2, 3], b: infer LEN) => any) ? LEN : never
```

Here we expect:
1. Type parameter `T` to be inferred as `[1, 2, 3]`
2. Type LEN to be inferred as `T['length']` -> `[1, 2, 3]['length']` -> `3`
3. Result to be inferred as `LEN` -> `3`

---

Case of [wrapping type checkers](https://dev.to/awerlogus/why-you-check-the-types-wrong-wrapping-type-checkers-ke):

```typescript
enum string_literal { string_literal = '' }

type StringLiteral<T> = T extends string ? string extends T ? string_literal : T : string_literal

type F4 = <T>(a: StringLiteral<T>, b: T) => T extends '' ? [] : T

// Now: all cases never

// Proposed: ['foobar', 'foobar']
type R4_1 = F4 extends ((a: 'foobar', b: infer P) => infer R) ? [P, R] : never
// Proposed: ['', []]
type R4_2 = F4 extends ((a: '', b: infer P) => infer R) ? [P, R] : never
// Proposed: [string_literal, string]
type R4_3 = F4 extends ((a: string, b: infer P) => infer R) ? [P, R] : never
```

---

# Use cases

In other similar issues I saw only use cases of function parameters inference, so I'll extend it with return type inference cases.

The first example is a `call` function:

```typescript
declare function call<P extends AnyTuple, F extends ((...args: P) => unknown)>(
  params: P, func: F
): F extends (...args: P) => infer R ? R : never

// Got: [unknown, unknown]
// Expected: [1, 2]
const a = call([1, 2], <A, B>(a: A, b: B): [A, B] => [a, b])
```

Also it may be used to infer return types on each step of computing `pipe` and `flow` function versions with unlimited parameters count. I'm sure, there're much more cases where this feature may be applied, but I can't remember any more right now.

# Util types used

```typescript
type Empty = readonly []


type AnyTuple = ReadonlyArray<unknown> & { readonly 0: unknown } | Empty


type Tail<T extends AnyTuple> = ((...args: T) => any) extends (head: any, ...tail: infer R) => any

  ? R extends AnyTuple ? Readonly<R> : never : Empty


type Prepend<T extends AnyTuple, E> = ((e: E, ...args: T) => any) extends

  (...args: infer R) => any ? R extends AnyTuple ? Readonly<R> : never : never


type _ReverseWith<T extends AnyTuple, Result extends AnyTuple> = {
  0: _ReverseWith<Tail<T>, Prepend<Result, T[0]>>
  1: Readonly<Result>
}[T extends Empty ? 1 : 0]


type Reverse<T extends AnyTuple> = _ReverseWith<T, Empty>
```