microsoft / microsoft/TypeScript

Assigning a callable return type of a generic function directly to some generic parameter results in error

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

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

Needs Investigation
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
1日 19時間
マージ済み PR(30日)
117

説明

TypeScript Version: above 3.3.3

Code

To see the actual behavior depending on a version of TS it is better to open the code below in the playground

type Callable<T> = { (t: T): any; };


declare function utilA<T>(
  callable: Callable<T>,
  fn: (t: T) => any
): any

declare function utilB<T>(
  callable: Callable<T>,
  params: {
    fn: (t: T) => any
  }
): any

declare function utilC<T>(
  params: {
    callable: Callable<T>,
    fn: (t: T) => any
  }
): any

/**
 * Case 1: directly assigning a callable
 */

declare const callable: Callable<number>

utilA(
  callable,
  t => t.toExponential() // t is a number, OK
)

utilB(
  callable,
  {
    fn: t => t.toExponential() // t is a number, OK
  }
)

utilC({
  callable,
  fn: t => t.toExponential() // t is a number, OK
})

/**
 * Case 2: directly assigning the result of a function returning a callable
 */

declare function computeCallableA(): Callable<number>

utilA(
  computeCallableA(),
  t => t.toExponential() // t is a number, OK
)

utilB(
  computeCallableA(),
  {
    fn: t => t.toExponential() // t is a number, OK
  }
)

utilC({
  callable: computeCallableA(),
  fn: t => t.toExponential() // t is a number, OK
})

/**
 * Case 3: directly assigning the result of a generic function returning a callable
 */

declare function computeCallableB<T>(t: T): Callable<T>

utilA(
  computeCallableB(100),
  t => t.toExponential() // t is a number, OK
)

utilB(
  computeCallableB(100),
  {
    fn: t => t.toExponential() // t is a number, OK
  }
)

utilC({
  callable: computeCallableB(100), // Error
  fn: t => t.toExponential() // t is an unknown
})

/**
 * The last case is strange IMO. The result type of `computeCallableB` is immediately known.
 * 
 * Hovering the last `utilC` shows the following:
 * 
 * function utilC<number>(params: {
 *   callable: Callable<number>;
 *   fn: (t: number) => any;
 * }): any
 * 
 * Despite the fact that TS infers generic `T` of `utilC`, assignment to `callable` param
 * gives: "Type 'Callable<number>' is not assignable to type 'Callable<unknown>'.".
 * 
 * It is hard to understand whats going on. More than that, everything works in TS v3.3.3.
 * In newer versions the behavior is broken.
 * 
 * This has an impact on the library I help to maintain. Such use cases are not rare and
 * people are forced to assign the result of `computeCallableB(100)` to a variable first,
 * and then to a `callable` parameter, which affects DX:
 */

const computeCallableBResult = computeCallableB(100)

utilC({
  callable: computeCallableBResult, // This works!
  fn: t => t.toExponential() // t is a number, OK
})

Expected behavior:

The invocation of

utilC({
  callable: computeCallableB(100), // Error
  fn: t => t.toExponential() // t is an unknown
})

gives no errors.

Actual behavior:

Strange behavior in versions above 3.3.3.

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

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

はじめの一歩

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

調査の方向性

まず、リンクされた TypeScript Playground の再現例を開き、Case 3 の呼び出しを、動作するケースおよび TypeScript 3.3.3 での動作と比較します。callablecomputeCallableB(100) を受け取る場合のインライン utilC オブジェクトに対するジェネリック推論を調査します。インライン呼び出しでエラーが発生せず、tnumber として推論され、中間変数を必要としない状態になれば完了です。

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

評価

技術スタック
typescript
領域
compilers
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
42/100

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

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