RuboCopで型情報を利用した解析を試している
3行
- RuboCopで「この変数はString」とかの情報を含めて解析させられないか試している
- まだPoCだけど、なんとなくは動いている
- https://github.com/pocke/rubocop-typed を見て
何をしているの
rubocop-typed というプロジェクトで、RuboCopで今までできていなかった解析ができないか試しています。
RuboCopの実行前にSteepを実行し、Steepの解析結果をCop内で使用することで型情報を利用した解析を実現しています。
何ができるの
現在は「nilになりえない式をレシーバーにした&.
でのメソッド呼び出し」に警告を出すCopのみを実装しています。
arr = [1, 2, 3] value = arr.find { |x| x == 1 } arr&.first # arrはnilになりえないので、警告が出る value&.chr # valueはnilになりうるのでok
とはいえこれは型検査器がやればいいことなので、もっとほかにできることはないかなと模索しています。
どうやってるの
RuboCopのプロセス内でSteepの解析を実行して型情報を保存しておき、適宜ASTから型情報を取り出して解析に利用しています。
たとえば先に上げた「nilになりえない式をレシーバーにした&.
でのメソッド呼び出し」では、RuboCopの機能であるon_csend
で&.
の呼び出しを見つけ、そのレシーバーの型をSteepに問い合わせ、型情報を見た結果nilになりえないと判断したら警告を出すようなコードになっています。
rubocop-typedは、rubocop-rspecなどと同じようにRuboCopのカスタムCopプラグインとして実装されています。
どうやって動かすの
現在このプロジェクトを動かすのはちょっと大変です。
まず、Gemfile内でSteepがpathで指定されているため、指定の位置にSteepをcloneしてきた上でセットアップをしておく必要があります。
https://github.com/pocke/rubocop-typed/blob/84622c7f6ce080dfdaa8552d0333c65632d576b2/Gemfile#L8
また、解析の対象となるプロジェクトにはSteepfileが置かれている必要があります。
現状と今後
とりあえず試しに書いてみて、なんとなく動かすことができそうだと言うことがわかりました。 ただし、実用としてはまだまだ遠いと考えています。 本格的に実用的に使うには、まずRubyの型システム自体がある程度実用に乗る必要があるかなと思っています。つまりSteepとruby-type-profilerあたりがいい感じになってくれると使いやすそうな気がします。
今後はまず型情報を利用したらどのような解析が可能になるのか、を考えたいと思っています。 型情報があることによって解析の夢が広がるような気はしているのですが、いまいちいい実用案が思いつきません。 そのため、なにかいい案がある方がいらっしゃいましたら適当に声をかけていただけるととってもありがたいです。 rubocop-typedのIssue、ruby-jp slackの #rubocop チャンネルなどであれば見落とすことなく対応できると思います。1
おそらく、今まで型なしで実装するとfalse positiveが多すぎて使い物にならなかったCopを実用的に実装することができるんじゃないかなと思っています。
また、既存のCopでも型情報を利用できたら嬉しいですね。
たとえばLint/Void
でbot
typeの後に式が来ていたら警告を出す、と言ったことができそうな気がします。
とはいえそのためにはRuboCop本体に手を入れる必要があり、現状のままだとうまくできそうにないのでどうやるのが良いのかなと考えています。
また現状はSteepのみと連携していますが、これは私がSteepのコードはちょっと読んだことがあって、Sorbetは全く読んだことがないという事情からきています。 解析器との連携をpluggableにして、Steepを使っている人はSteepで、Sorbetを使っている人はSorbetでRuboCopも解析できたら良いとは思いますが、実装は地獄の道な気がしています。
まとめ
rubocop-typed を試しに作ってみたので、ダラダラと記事を書いてみました。なんかいいアイディアあったら教えてください。