microsoft / microsoft/TypeScript

Function parameters and return type inference with infer keyword

オープン
#38,182 コメント 2 件 リアクション 10 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

In Discussion Suggestion
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
2日 4時間
マージ済み PR(30日)
132

説明

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>

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

まず、conditional type の例と、issue に記載されている現在の結果と提案された結果を確認します。実装ファイル、テスト、エントリポイントは指定されていないため、関連する型推論サブシステムを特定し、互換性のあるテストを定義することも作業の一部です。提案されたパラメーター型と戻り値型の推論ケースが記載された結果を生成すれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
typescript
領域
compilers
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
25/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。