kateinoigakukunのブログ

思考垂れ流しObserver

TypeProf for IDEの開発をお手伝いしました at クックパッド

TL;DR

インターンの内容

TypeProfはクックパッドでフルタイムRubyコミッタをされている@mametterさんが開発しているRubyの型プロファイラです。 Rubyのプログラムにできるだけ型注釈を入れずに抽象解釈によって型を推論する、という面白い特徴があります。

Ruby 3.0ではRBSのプロトタイプを生成するためのツールとしてRubyにバンドルされています。

github.com

今回のインターンでは、TypeProfの解析結果を利用したRubyのLanguage Serverの実装をお手伝いしました。

TypeProf for IDEについては今年のRubyKaigi Takeout 2021のKeynoteで発表があったので、雰囲気を知りたい方はこちらを見てください。

www.youtube.com

www.slideshare.net

実装

そもそもRubyを触るのが久々かつ、TypeProfがそこそこ大きいプログラムであったため、binding.irbデバッグ可能な環境を作るところからはじめました。

これを初手で準備したことでその後のコードリーディング効率が格段に上がりました。やはりデバッガは偉大。

Add --port option to lsp server for debugging purpose by kateinoigakukun · Pull Request #34 · ruby/typeprof · GitHub

その後mameさんに助けてもらいながら数種類のコードジャンプを実装しました。 ジャンプ先候補はTypeProfの解析結果を使っているので、解析器自体の実装も勉強できて良かったです。

もうひとつ面白い改善として、コード補完の高速化を行いました。

TypeProf for IDEではユーザが1タイプするごと (textDocument/didChange) にプログラムを解析し直すのですが、解析の仕組み的に一回の解析にかなり時間がかかります。 オリジナルの実装では解析中サーバスレッドをブロックしていたため、タイプするごとに解析待ちのリクエストが溜まっていました。

さらに、コード補完 (textDocument/completion)では変更後のプログラムを検証する解析とは別の解析がサーバスレッドで行われているため、1タイプごとに貯まる重いタスクが更に増え、若干もっさりしていました。

LSPではdidChangeは通知であり、すぐに結果を返却しなくても良く、更にコード補完リクエストもリクエストとレスポンスの順序を気にしないメソッドなので、高速化のために解析スレッドを分けることにしました。 また、新しい didChange 通知が来た時点で前の状態での解析結果は無効になるため、解析を途中で打ち切る機構を追加しました。

f:id:kateinoigaku:20210912110945p:plain

before after
typeprof-before typeprof-after

github.com

その他にも色々と実装できて楽しかったです。

インターン中のPRs

Ruby本体への貢献

TypeProf for IDEは最新の開発版MRI (Matz's Ruby Interpreter) のAPIを使っており、CIではデイリービルドされた最新のRubyバイナリを使っています。

具体的には、ruby/setup-rubyGitHub Actionsのアクション経由で ruby/ruby-dev-builder でビルドされた成果物を使っていました。

しかし、ある日直近で入ったAPI変更に追従する変更をTypeProfに入れたところ、CI上のテストが失敗し始めました。

調査したところ、ruby/ruby-dev-builderのデイリービルドがmacOS上で数十日間失敗しており、最新のバイナリが全くアップロードされていないことがわかりました。具体的には、macOSにデフォルトで同梱されているGNU Makeのバージョンが古いことが原因で、最新のruby/rubyMakefileを正しく解釈できていませんでした。

そして、特定のビルドオプションを付けたときのみ再現する問題であったため、ruby本体のCIを奇跡的に通り抜けていました。

macOSはGPLv3を避けるためにGNU Makeのバージョンを3.81で止めている。ちなみに3.81は2006年リリース。最新は4.2

とりあえずの対処としてGNU Make 3.81で最新のバージョンと同様の動作をさせるためのワークアラウンドを追加しました。

github.com

ということで晴れてRuby Contributorの実績も解除できました。

クックパッドの環境

ruby-devチームの朝会では@_ko1 さんと@mametterさんがRubyの話を、僕がSwiftとWebAssemblyの話をする機会があったり、母国語で言語処理系のプロと働けるとても貴重な環境でした。

感想

Ruby自体の経験は浅かったものの、言語処理系や巨大プロジェクト開発の経験のお陰で、ある程度の貢献ができたかなと思います。

また、Ruby開発には単純に人手が足りず、自分でも貢献できそうなインパクトの大きいタスクが残っていたり、出来ることはたくさんありそうなので、時間を見つけて継続して関わっていけたらなと思います。

今回のインターン中関わってくださった皆さんありがとうございました。