Flowのnullチェックとanyへのキャスト

Flowはnullに対するチェックが厳しく、nullを想定していない場所にnullを渡したり、nullが渡される可能性があったりすると、エラーを出してくれる。

例えば以下のコードでFlowを実行すると、エラーが出る。

// @flow

type Owner = {
  name: string,
  age: number,
};

type Repository = {
  id: number,
  isPublic: boolean,
  owner: Owner | null,
};

function showOwnerName(owner) {
  console.log(`This owner is ${owner.name}.`);
}

function showRepositoryData(repository: Repository) {
  console.log(`Id is ${repository.id}.`);
  console.log(repository.isPublic ? 'This is public.' : 'This is not public.');
  showOwnerName(repository.owner);
}

const exampleRepo = {
  id: 23,
  isPublic: true,
  owner: {
    name: 'Tom',
    age: 50,
  },
};

showRepositoryData(exampleRepo);

このプログラムそのものは問題なく動き、ログが表示される。

まずRepositoryというデータ構造があり、idisPublicownerというデータを持っている。
そしてownerは、nameageを持っている。
ただし、全てのRepositoryownerを持っているとは限らず、その値がnullである可能性もある。

この「ownerがnullの可能性がある」というのが、問題になる。

上記のサンプルではownerはnullではないが、型の定義でnullもあり得るとしている以上、プログラム的には、ownerがnullである可能性は否定できない。というか、そういう可能性があるからこそ、owner: Owner | nullと定義しているのだ。

そしてownerがnullのときにowner.nameにアクセスしようとすると、エラーになる。

そのため、上記のコードをFlowでチェックするとエラーになる。
プロパティにアクセスしているけど、そのときにownerがnullである可能性がありますよ、と。

property `name`. Property cannot be accessed on possibly null value

nullチェック

nullかどうかをチェックするようにすれば、エラーは出なくなる。

function showOwnerName(owner) {
  if (owner) console.log(`This owner is ${owner.name}.`);
}

また、仕様上、ownerがnullのときはshowOwnerNameは呼ばれないようになっている、呼ばれてはいけない、というのなら、nullのときは例外を投げるようにするのがいいかもしれない。

function showOwnerName(owner) {
  if (!owner) throw new Error('owner is null.');
  console.log(`This owner is ${owner.name}.`);
}

anyへのキャスト

ownerが確実にnullではない、というケースなら、anyにキャストしてしまうという方法もある。

function showOwnerName(owner) {
  console.log(`This owner is ${((owner: any): Owner).name}.`);
}

このように書くことで、ownerの型を好きなもの(上記の場合Owner)にキャストできる。
ただ、公式ドキュメントThis is unsafe and not recommended.とあるように、型チェックを放棄してしまっているわけで、基本的には使うべきではない。
ただ、こういう書き方も出来ると知っておくと、どこかで役に立つこともあるかもしれない。

参考資料