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[] のサブタイプであることはそのままです。