こんにちは、フルタイムRubyコミッタとして働いてる遠藤(@mametter)です。昨日、Ruby 3.0.0-preview2がリリースされました!
このリリースには、遠藤が開発している Ruby の静的型解析ツール TypeProf が初めて同梱されています。これの使い方をかんたんにご紹介したいと思います。
デモ
TypeProf は、型注釈のない Ruby コードを無理やり型解析するツールです。とりあえずデモ。
# user.rbclassUserdefinitialize(name:, age:) @name = name @age = age endattr_reader:name, :ageendUser.new(name: "John", age: 20)
typeprof コマンドは、Ruby 2.7 で gem install typeprof
でインストールできます *1。TypeProf にこの Ruby コードを与え、typeprof user.rb -o user.rbs
と実行してください。次のような内容の user.rbs が生成されているはずです。
# ClassesclassUserattr_readername: Stringattr_readerage: Integerdef initialize: (name: String, age: Integer) -> Integerend
TypeProf は、与えられた Ruby コードの型情報を推定して出力します。user.rbs は、Ruby ではなく、RBS という Ruby 3 標準の型情報記述の言語で書かれています。雰囲気でなんとなく読めるかと思いますが、たとえば def initialize: (name: String, age: Integer) -> Integer
は name
というキーワード引数が String
インスタンスを受け取り、age
というキーワード引数が Integer
インスタンスを受け取る、ということを表現しています。返り値は Integer
ですが、initialize
の返り値はあまり意味がないですね。
TypeProf の特徴は、メソッド呼び出しの情報をフル活用するところです。これは従来のふつうの型解析と本質的に異なるところです。これにより、def initialize(name:, age:) ... end
という型注釈が一切ない定義に対しても、User.new(name: "John", age: 20)
という呼び出しで渡される型を見てそれっぽい型情報を推定します。
もうひとつデモ
TypeProf は、Ruby コードだけでなく RBS も合わせて解析できます。デモ。
# test.rbdefhello_message(user) "The name is " + user.name enddeftype_error_demo(user) "The age is " + user.age end user = User.new(name: "John", age: 20) hello_message(user) type_error_demo(user)
# user.rbsclassUserdef initialize: (name: String, age: Integer) -> void attr_readername: Stringattr_readerage: Integerend
user.rbs は、前の例の出力を少しだけ手修正したものです。これと test.rb をあわせて解析します。
typeprof -v test.rb user.rbs
と実行してください。
# Errors test.rb:7: [error] failed to resolve overload: String#+# ClassesclassObjectprivatedef hello_message: (User) -> Stringdef type_error_demo: (User) -> untyped end
コマンドライン引数の -v
はエラーの可能性を表示させるオプションです。
このため、今度は # Errors
という出力があります。
test.rb の 7 行目を見てみると、"The age is " + user.age
という計算をしていますが、これは String
と Integer
を結合しようとしています。
TypeProf はこれをバグとして警告しています。
class Object
から end
は、先程と同様に test.rb の型情報を推定したものです。
なお、7 行目に型エラーの可能性があって型の追跡ができなくなったため、type_error_demo
メソッドの返り値は untyped
となってます。
Ruby TypeProf Playground
TypeProf は Ruby TypeProf Playgroundでブラウザ上で試せます *2。
- user.rb を解析するデモ(上記の 1 つめの例)
- test.rb + user.rbs を解析するデモ(上記の 2 つめの例)
左上が Ruby コード、左下が RBS(書かなくても良い)で、Analyze ボタンを押すと右側に解析結果が表示されます。 Ruby コードをいじって解析することもできるので、期待に反する挙動を見つけたら Report bug ボタンでぜひ報告してください *3。
TypeProf の課題と現状
TypeProf は「型注釈を書かない選択肢を Ruby に残す」ということを至上命題とした極端な設計になっているので、様々な問題点もあります。
そのため Ruby 3.0 の TypeProf では、型注釈のない Ruby コードに対して RBS スタブを生成する、という機能にフォーカスして設計・実装を進めました *6。 RBS スタブ生成機能として経験を積みながら、高速化や解析精度向上を進め、将来的にはかんたんな型検査器として使えるものにできたらいいなと思っています。
まとめ
Ruby 3 に同梱される型解析ツール TypeProf をご紹介しました。かんたんな使い方にフォーカスして書いたので、もう少し詳しいことはドキュメントや過去の発表をご参照ください。
TypeProf の現状の完成度としては、Ruby パッケージに含まれるすべての .rb ファイルで解析が通る *7ことを確認できた程度です。出力の精度評価や速度向上はまだまだこれから頑張っていきます。ぜひ遊んでみて、気づいたことがあったらバグ報告でも感想でもいただけると泣いて喜びます。
RBS と TypeProf の関係は? Steep や Sorbet というのも聞いたが?
Ruby 3 の静的解析は固有名詞が多くてややこしいので、関係を別記事にまとめました。
*1:ruby 3.0.0-preview2 なら gem install なしで typeprof コマンドが利用可能です。
*2:社内で「こういうのを作りたい」と語ったら、id:koba789さんが 1 時間で作ってくれました。
*3:とても適当に運用しているので、サーバが落ちたらごめんなさい。
*4:ふつうの型解析は基本的にメソッド単位で解析を行いますが、TypeProf は呼び出し元をたどる必要があるため、大変になりがちです。なお、RBS 言語を部分的に手書きすればするほど(理論上は)早くなります。
*5:解析のカバレッジを上げるため、スタブ実行というヒューリスティクスを実装しています。これは、どこからも呼ばれなかったメソッドに untyped な引数を与えて無理やり呼び出すものです。これにより、テストがなくても一通りの解析は行えるようになっています。
*6:なお、rbs コマンドにも Ruby コードから RBS スタブを生成する機能がありますが、こちらは基本的にすべての引数を untyped として出力するものです。やることが単純な分、速くて安定しているというメリットもあります。
*7:TypeProf が理不尽に例外終了しないことを確かめた程度ですが、やんちゃなコードだらけの test/ruby や ruby/spec はそれだけでも地獄の苦しみでした。