初めてflow使って気になった点・ハマった点

[Flow](https://flowtype.org/) を使い始めて、気になったりハマったりで解決に多少の時間を要した点のメモ集です。

マニュアルを読めば解決していたなど、普通大丈夫だろみたいな点も含まれていると思います。

バージョンは、特に注釈をしていない場合は `v0.40.0` であるものとします。

随時追加予定です。


## 環境設定
### 設定しないですぐ試してみたい

[Try Flow](https://flowtype.org/try/) という公式の Web サービスを使えば、今すぐ試すことができます。


### ~~`"flow; test $? -eq 0 -o $? -eq 2"` は何ですか~~

過去バージョンの Getting Started で用意されていた npm スクリプトでした。

`v0.43.1` では `"flow": "flow"` に[変わっていました](https://flow.org/en/docs/install/)。

### シェル上で実行したい

`flow check-contents` を使います。

```bash
echo 'var s: string = ""; s = 1;' | flow check-contents
-:1
  1: var s: string = ""; s = 1;
                             ^ number. This type is incompatible with
  1: var s: string = ""; s = 1;
            ^^^^^^ string


Found 1 error
```

### `[libs]` は `./〜` では指定できない

False)

```:.flowconfig
[libs]
./decls/
```

True)

```:.flowconfig
[libs]
decls/
```

マニュアル該当箇所) https://flowtype.org/docs/advanced-configuration.html#libs

"These paths can be relative to the root directory or absolute." と書いてあるのでてっきり "./" スタートなのかと思いこんでいていました。

### `".js.flow" style` の declarations が反映されない

False)

```:foo.js
// @flow
/* Your JS code.. */
```

```:foo.js.flow
// @flow
/* Your Flow code.. */
```

True)

```:foo.js
/* Your JS code.. */
```

```:foo.js.flow
// @flow
/* Your Flow code.. */
```

マニュアル該当箇所) https://flowtype.org/docs/declarations.html#declaration-files

`// @flow` は `.js` 側には記載してはいけません。

## `[include]` や `[libs]` に新しく置いたファイルが認識されない

flow サーバを一度止めましょう。

```bash
# 止める
flow stop
# 次の実行から読み込まれる
flow
```


## 型アノテーション
### Sealed object types

Object 型のアノテーションを付与すると、「存在しないプロパティへアクセスするとエラーをレポートする」状態になります。

これを、[Sealed object types](https://flowtype.org/docs/objects.html#sealed-object-types) と呼ぶようです。[^1]

例として、あるプレーンオブジェクトの存在しない `x` プロパティへアクセスするコードを、型アノテーションの有り無しそれぞれの状況で Flow でチェックしてみましょう。

型アノテーション無し)

```bash
echo 'const obj = {}; obj.x; obj.x = 1;' | flow check-contents 
No errors!
```

型アノテーション有り)

```bash
echo 'const obj: {} = {}; obj.x; obj.x = 1;' | flow check-contents
-:1
  1: const obj: {} = {}; obj.x; obj.x = 1;
                             ^ property `x`. Property not found in
  1: const obj: {} = {}; obj.x; obj.x = 1;
                         ^^^ object type

-:1
  1: const obj: {} = {}; obj.x; obj.x = 1;
                                    ^ property `x`. Property not found in
  1: const obj: {} = {}; obj.x; obj.x = 1;
                                ^^^ object type


Found 2 errors
```

後者は参照と代入それぞれでエラーになりました。

しかし、型アノテーションが無い場合でも有りと同じ状態になることがあります。
それは、中身が空ではないプレーンオブジェクトを定義した場合です。

型アノテーションは無いが空ではないプレーンオブジェクトを宣言した場合)

```bash
echo 'const obj = { y: 2 }; obj.x; obj.x = 1;' | flow check-contents
-:1
  1: const obj = { y: 2 }; obj.x; obj.x = 1;
                               ^ property `x`. Property not found in
  1: const obj = { y: 2 }; obj.x; obj.x = 1;
                           ^^^ object literal

-:1
  1: const obj = { y: 2 }; obj.x; obj.x = 1;
                                      ^ property `x`. Property not found in
  1: const obj = { y: 2 }; obj.x; obj.x = 1;
                                  ^^^ object literal


Found 2 errors
```

さて、この仕組が問題になりやすい状況として、`Object.assign` によるプロパティの拡張があります。

また例を出します)

```bash
%echo 'Object.assign({ x: 1 }, { y: 2 });' | flow check-contents
-:1
  1: Object.assign({ x: 1 }, { y: 2 });
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `y` of object literal. Property not found in
  1: Object.assign({ x: 1 }, { y: 2 });
                   ^^^^^^^^ object literal


Found 1 error
```

これがエラーになるのは、第一引数のプレーンオブジェクトが空ではないため sealed として認識されているのに、第二引数で拡張しているからです。

この場合は、第一引数を `{}` にすると解決します:

```bash
$echo 'Object.assign({}, { x: 1 }, { y: 2 });' | flow check-contents
No errors!
```

ロジックは微妙に変わってしまいますが、個人的には許容範囲内かなと感じています。

参考 Issue) [Object.assign({}, objOfType) causes `property not found in object literal` error #1805](https://github.com/facebook/flow/issues/1805)

### Union や Intersection を使ったら意図せぬエラーがでる

自分の場合、[Union と Intersection](https://flowtype.org/docs/union-intersection-types.html) に対する理解がそもそも誤っていました。

解決に当っては、以下の記事が参考になりました。
[関数オーバーロードとIntersection types](http://qiita.com/kuy/items/f342fbd1737f557cf42a)

一文を引用しておきます。

> どちらの型が入っているかわからないもの（Union types）に対して呼び出すには、どちらの型が入っていても問題ないように呼び出し可能かチェックする部分で引っかかっていたわけです。

### 関数の戻り値に対する定義を確認する際のショートハンド

手元で関数の戻り値に対する定義の動作確認をする際に、例えば以下のように書く代わりに、

```bash
echo 'const x: string = Math.random()' | flow check-contents                       
-:1
  1: const x: string = Math.random()
                       ^^^^^^^^^^^^^ number. This type is incompatible with
  1: const x: string = Math.random()
              ^^^^^^ string


Found 1 error
```

この様に書くこともできます。

```bash
echo '(Math.random(): string)' | flow check-contents
-:1
  1: (Math.random(): string)
      ^^^^^^^^^^^^^ number. This type is incompatible with
  1: (Math.random(): string)
                     ^^^^^^ string


Found 1 error
```

じゃあ、括弧も省略できそうじゃん！とやってみると構文エラーです。

```bash
echo 'Math.random(): string' | flow check-contents 
-:1
  1: Math.random(): string
                  ^ Unexpected token :


Found 1 error
```

公式ドキュメントを眺めていて知った記法なのですが、何故こうなるのかは不明です。

----

[^1]: [この記事](https://also.github.io/notes/2016/03/13/flow-object-assign.html) では、そのエラーが出る状態を "sealed"、そうではない状態を "unsealed" と呼んでいますが、型が付与されている中でその両者の区分があるのか、"unsealed" は型の無い状態を意味しているのか、は不明です。