TypeScript 2.4 RCがリリースされました。
Announcing TypeScript 2.4 RC | TypeScript
いくつか変更点があるのですがこのエントリではSafer callback parameter checkingについて解説します。公式ドキュメントでいうとFAQ · Microsoft/TypeScript Wiki · GitHubあたりの話に関連します。あるいはなぜ TypeScript の型システムが健全性を諦めているかとも関連します。
Dog[]
は Animal[]
のサブタイプか
TSでは Dog
が Animal
のサブタイプである時、Dog[]
は Animal[]
のサブタイプです。型システムとして健全かどうかは置いといて便利なのでこうなっています。
さて、Dog[]
が Animal[]
に代入可能かどうかを判定する際コンパイラは最終的に、(x: Dog) => number
は (x: Animal) => number
に代入可能かを調べることになります。ではこれをどうやって判定するのでしょうか?答えは「Dog
がAnimal
に代入可能もしくはAnimal
がDog
に代入可能」かで判定します。
つまり「Dog
がAnimal
に代入可能もしくはAnimal
がDog
に代入可能 ならば(x: Dog) => number
は(x: Animal) => number
に代入可能」ということです。
閑話
本筋とはズレますが、Dog[]
は Animal[]
のサブタイプとしたときに、型システムとして健全性が崩れる例です。
class Animal { } class Dog extends Animal { bark(): void {} } let a = [new Animal] let d = [new Dog] a = d; a[0] = new Animal for ( let item of d) { item.bark() } // => Uncaught TypeError: item.bark is not a function
この仕様の問題点
Dog[]
は Animal[]
のサブタイプであることの問題点はさておき、「Dog
がAnimal
に代入可能もしくはAnimal
がDog
に代入可能 ならば(x: Dog) => number
は(x: Animal) => number
に代入可能」となることが問題になります。
アナウンスブログからの引用でいうと
interface Animal { animalStuff: any } interface Dog extends Animal { bark(): void } interface BasicCollection<T> { forEach(callback: (value: T) => void): void; } declare let animalCollection: BasicCollection<Animal>; declare let dogCollection: BasicCollection<Dog>; // This should be an error, but TypeScript 2.3 and below allow it. dogCollection = animalCollection;
dogCollection
にanimalCollection
が代入可能かどうかは最終的にはインターフェースBasicCollection
のforEach
の引数になっているcallback
部分が代入可能化どうかで判定されます。つまりcallback: (value: Dog) => void
にcallback: (value: Animal) => void
が代入可能かどうかですが、配列の例で見た時と同じロジックで代入可能と判断されdogCollection = animalCollection
はエラーになりません。
具体的にcallback
を
dogCollection.forEach((value: Dog) => {value.bark()});
だと想定すると実行時にエラーになるのがわかると思います。
これはPromise<T>
におけるthen
にも当てはまりますので、Promise<Animal>
をPromise<Dog>
に代入可能なことになってしまいます。
2.4からどうなるのか
callback関数の判定時は特別に(x: Dog) => void
は (x: Animal) => void
に代入可能かの判定がAnimal
がDog
に代入可能かで判定されるようになります。
というわけでdogCollection = animalCollection
はエラーとなり、animalCollection = dogCollection
は(引き続き)エラーになりません。またPromise<Animal>
をPromise<Dog>
に代入することもできなくなります。
TS 2.4以前(playdroundがアップデートされるまではエラーが出ない様子が見れると思います)。
2.4以降は以下。
interface Animal { } interface Dog extends Animal { someProperty: string } let a: Promise<Animal>; let d: Promise<Dog>; d = a; // => // a.ts(11,1): error TS2322: Type 'Promise<Animal>' is not assignable to type 'Promise<Dog>'. // Type 'Animal' is not assignable to type 'Dog'. // Property 'someProperty' is missing in type 'Animal'.
まとめ
Dog[]
をAnimal[]
のサブタイプにするためにPromise<Animal>
がPromise<Dog>
に代入可能でしたが、2.4からはPromise<Animal>
はPromise<Dog>
に代入できなくなります。Dog[]
をAnimal[]
のサブタイプであることはそのままです。