FlowのObject type spreadの挙動を確認する

  • 2
    Like
  • 0
    Comment

はじめに

v0.42.0 からリリースされている Object type spread 機能ですが、ドキュメントが 公式サイト にはまだ無いようです。
そこで実際挙動を調査したところ、個人的には中々難しい仕様だったので、まとめてみました。

検証に使った flow-bin のバージョンは v0.45.0 です。

基本的な挙動

サンプルとして、以下のような型を定義してみます。

type T1 = {
  x: number,
  y: number,
  z: number,
};

type T2 = {
  y: number,
  z: string,
  a: number,
  ...T1,
  b: number,
};

T2 には複数のプロパティが定義されていますが、それぞれこのような意図で定義しています。

  • x = 拡張する側にあり、拡張される側にはない
  • y = 拡張する側にあり、拡張される側に同じ型がある
  • z = 拡張する側にあり、拡張される側に違う型がある
  • a = 拡張される側にあり、拡張される前に宣言されている
  • b = 拡張される側にあり、拡張される後に宣言されている

その結果、T2 はどのような定義になるかと言うと、以下と同じになるようです。

type T2_ = {
  x?: number,
  y:  number,
  z:  number | string,
  a:  mixed,
  b:  number,
};

// 確認コード
({ x: 0, y: 0, z: '', a: [], b: 0 }: T2);
({ x: 0, y: 0, z: '', a: [], b: 0 }: T2_);
({ x: 0, y: 0, z: 0, a: [], b: 0 }: T2);
({ x: 0, y: 0, z: 0, a: [], b: 0 }: T2_);
({ y: 0, z: '', a: [], b: 0 }: T2);
({ y: 0, z: '', a: [], b: 0 }: T2_);
({ x: 0, y: 0, z: '', a: [], b: 0, newProp: 0 }: T2);
({ x: 0, y: 0, z: '', a: [], b: 0, newProp: 0 }: T2_);

それぞれを挙動から推測して、理由付けしてみます。

  • x?: number
    • ...T1 によって増やされたプロパティです
    • 型は T1 と同じになりますが、オプショナルになります
  • y: number
    • ...T1 の前に T2 の型宣言に含まれているプロパティです
    • 型は両方の Union になりますが、number | number なので、結果 number になっているようです。たぶん。
  • z: number | string
    • y と同じように、...T1 の前に T2 の型宣言に含まれているプロパティです
    • 同じように両方の Union になりますが、y と違って両方の型が異なるので number | string になっているようです
  • a: mixed
    • ...T1 の前に T2 の型宣言に含まれているプロパティですが、T2 に同じプロパティが存在しなかったプロパティです
    • これは理由が良くわかってないので、説明は後にします
  • b: number
    • ...T1 の後に T2 で型宣言されているプロパティですが、T2 に同じプロパティが存在しなかったプロパティです
    • これは b の定義がそのまま有効になっているようにみえます

こんな風に見えました。

参考 Issue

唐突にここで Object spread type について質問とその回答がなされている Issue の紹介です。全般的に役立ちました。

https://github.com/facebook/flow/issues/3534

ここに先程の a: mixed の理由も書いてあるよう ですが・・・
・・・自分はよくわかりませんでした・・・。

自分が欲しかったものは ...$Exact<T1> すれば良い

さて、上記の中からある一部の挙動だけを抽出してサンプルにしてみます。

type T1 = {
  additional: number,
};

type T2 = {
  existing: number,
  ...T1,
};

// 以下と同じになる
type T2_ = {
  existing: mixed,
  additional?: number,
};

現実は上記のようになりますが、自分としては以下のように、T2T1 分だけ増やされる定義になることを期待していました。

type T2 = {
  existing: number,
  additional: number,
};

これには先に紹介した Issue で解が用意されており、...$Exact<T1> しろとのことです。

Ref) https://github.com/facebook/flow/issues/3534#issuecomment-287580240

type T1 = {
  additional: number,
};

type T2 = {
  existing: number,
  ...$Exact<T1>,  // これな
};

// 以下と同じになる
type T2_ = {
  existing: number,
  additional: number,
};

何故こうなるのかは・・・これも不明です・・・。

以下らの概念と関係があるらしいのですが、{ ...exactな定義 } がこういう結果になるという明確な理解は得られていません。

以上

マニュアル待ちというステートです。