Intersection of merged declarations - function types behave inconsistently

オープン
#57,319 コメント 5 件 リアクション 0 件 担当者 0 名 GitHub で見る

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

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
35/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
停滞
技術スタック
typescript
領域
compilers

調査の方向性

まず、リンク先の TypeScript Playground の再現コードを実行し、マージされた Foo と Bar の宣言を union の呼び出し動作と比較します。次に、関連する issue #19064 の議論を読み、マージされた関数型と union を担当する型チェックの経路を追跡します。union の呼び出し結果と ReturnType が一致し、固有の shuffle メソッドが維持されれば完了です。

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

説明

In Discussion Suggestion
🔎 Search Terms

declaration merging

🕗 Version & Regression Information
  • This is a type inference problem
  • This problem appears in Typescript 3.3.3, 3.5.1, 3.9.7, 4.9.5, 5.3.3 and 5.4.0-dev.20240207
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about declaration merging
⏯ Playground Link

https://www.typescriptlang.org/play?#code/C4TwDgpgBAsiCCAnRBDEBRAHsCA7AzgJYD2uAPACoB8UAvFEqiJTQGRQDeU+AFgK4AzAQBsIACgCUALigA3YoQAmUAL4AoNYVw5EAlAGNoAMWLFOUAOYRgAGUL5gkmYzRkHiLRZrqtOvYagTMy4rW3tHaVgEZDQsHAIScndPb01tCF0DaAAhFERzULsHJwYY5mTcL1U0vyyoXPyQ6yKImTgXDGw8IlI3YA9K1LVQSEDTFopwaHoAJWs+RFxJyDIggG0AIkLwjYBdGihDo4B6Y6gUfCgITEh9HEUZCos13ah2Dl5BEXFI+SUAbnUIxyeQmUzoUDmwAWSymZAam22Dj2ByOh1O50u11u90e-U8LzenE+QlEJT+ikBw3BRlB4WW00h80WDLIYiCAB8GhJEc0dvsoBiLlcbhA7hAHtx8ZVCe8Sd9yQpKeo1KJgFABKYjLgIWIOCpMWNiBIAHRI4D-NFW61WjFcEodPoDLyWkrtMpxbqJJ0pS3qNVQABGeW1uv1hu5Zr5DktNrjaLtUAdZR9g1dkXdTE9CV6TyoftV1g1IZ19D1BuFnMj5tj8dtZ3tkUdefTbWiWa6OaS0pdqigHPMyaYqd7bvbsU7PW7zvz1QDekQLRktMX9PB9AX2sktbrCbOvGIfGEygA7sREABrAA0Qb46oyiHPUjUxwAVFA1AyoAByJ4vb9QPYUC4MQ6oXEQFi4CggaiFAwBmMCP6ZhO8RTiOVDfiaaiHAACo+kCIKAP7yqIAFAQAtvYRCVIBOqIb+Pb-re6qIBAACOfCEKxyhaHB4LflwJE-DIFJ+phYgAEwAMwSRJEhqK+xwaOWhpBKa5qSCaQnbru8YYmel74GoKnCtW0YRFp-Ckj8O66XuUAGReRkmZcVZ5Op5madpEi2buGLoMgT5QHhxAEURv5Wd8AGKMQECXCB96YOEUCkHxowMc6-4mpJUlSQAnBIQA

💻 Code
type MyArrayExtension<T> = Array<T> & { shuffle(): void }

interface Foo { getList(): Array<string> }
interface Foo { getList(): MyArrayExtension<string> }
interface Bar { getList(): Array<string> }
interface Bar { getList(): MyArrayExtension<string> }

type FooListType = ReturnType<Foo["getList"]>       // as expected: string[] & {shuffle(): void;}
type BarListType = ReturnType<Bar["getList"]>       // as expected: string[] & {shuffle(): void;}
type FarListType = ReturnType<(Foo|Bar)["getList"]> // as expected: string[] & {shuffle(): void;}

let fooFn = ({} as Foo).getList;                    // { (): Array<string>; (): MyArrayExtension<string>; }
let barFn = ({} as Bar).getList;                    // { (): Array<string>; (): MyArrayExtension<string>; }
let farFn = ({} as Foo|Bar).getList;                // { (): Array<string>; (): MyArrayExtension<string>; } | { (): Array<string>; (): MyArrayExtension<string>; }
let farList: FarListType = farFn();                 // should work, but error:
/* 
Type 'string[]' is not assignable to type 'MyArrayExtension<string>'.
  Property 'shuffle' is missing in type 'string[]' but required in type '{ shuffle(): void; }'.(2322)
*/

({} as Foo).getList().shuffle();                    // works
({} as Bar).getList().shuffle();                    // works
({} as Foo|Bar).getList().shuffle();                // Error: Property 'shuffle' does not exist on type 'string[]'.(2339)
🙁 Actual behavior

There are two interfaces (Foo and Bar), that each are constituted of two merged declarations.
Each interface individually behaves such that the later declaration wins (getList(): MyArrayExtension<string> in either case). When changing the order (define the MyArrayExtension returning interfaces first), the return type accordingly is string[].
However, when intersecting Foo|Bar, the behaviour becomes inconsistent:

  • ReturnType<(Foo|Bar)["getList"]> is what I would expect (compatible with MyArrayExtension<string>), but
  • ({} as Foo|Bar).getList() is not assignable to a variable of this type
🙂 Expected behavior

My use case is the following: I have an external dependency that defines interfaces with functions that return T[].
Since I know those functions return a specific type that extends T[], I want to augment the module (using declare module) and give the functions a more specific return type.
The behaviour for non-intersected types if perfect for this. The later (e.g. provided by my declare module augmentation) wins.
However, it breaks down when intersecting two types from the external dependency, as described above.

I would expect type inference to work consistently. I think it's good to be able to override/make types more specific this way, and I think this ability should not be lost when intersecting types for which this has been done.

Additional information about the issue

@jcalz explained to me on StackOverflow that this problem is not restricted to module augmentation, but appears even when merging declarations in the same file. The problem is probably related to #19064.

主要言語
Go
スター
111k
フォーク
14.4k
平均マージ
1日 15時間
マージ済み PR(30日)
106

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

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

はじめの一歩

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

microsoft/TypeScript のほかの issue

microsoft/TypeScript の issue をすべて見る

似ている issue

Go の issue をもっと見る

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

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