microsoft / microsoft/TypeScript

Function parameters and return type inference with infer keyword

Abierto
#38,182 2 comentarios 10 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

In Discussion Suggestion
Lenguaje dominante
Go
Estrellas
111k
Forks
14.4k
Merge medio
1 d 19 h
PR fusionados (30 d)
117

Descripción

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:

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]

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]]

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:

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:

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

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>

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza revisando los ejemplos de tipos condicionales y los resultados actuales frente a los propuestos documentados en el issue. No se nombran archivos de implementación, pruebas ni puntos de entrada, por lo que localizar el subsistema relevante de inferencia de tipos y definir pruebas compatibles forma parte del trabajo. Se considera terminado cuando los casos propuestos de inferencia de tipos de parámetros y valores de retorno producen los resultados indicados.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
typescript
Área
compilers
Tipo de issue
Nueva funcionalidad
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Estancado
Claridad
Bastante claro
Aptitud para principiantes
25/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.