YARD
YARDはドキュメンテーションツールです。 Rubyのコメントに
# @param [String] a # @return [void] def foo(a) end
みたいな記述を見たことがありませんか?
この@param
がYARDのタグ名、[String]
がタグのもつ型情報です。a
は引数の名前ですね。
YARDではこの型情報を元にリンクを貼ったwebページを生成したりできます。
rubydoc.infoがまさにYARDによって生成されていますね。
RBS
RBSはRuby公式の型を記述する書式、およびツールの名前です。
こんなかんじです。
def foo: (String a) -> void
YARD to RBS
では
# @param [String] a # @return [void] def foo(a) end
とかかれたソースコードを読み込んで
def foo: (String a) -> void
とかかれたRBSを出力できたら便利だと思いませんか?
orthoses-yard
というわけで実装したのがorthoses-yardです。
orthoses-yardはorthosesの拡張です。 orthosesが何かは、RubyKaigi2022で紹介した資料を御覧ください。動画はもうすぐ出ると思います。*1
orthoses-yardではこのorthosesの仕組みに乗っかって、任意のpathのRubyをパースしたYARD情報からRBSを生成します。 「アプリケーションの一部だけしかYARDドキュメント書けてないんだよな」という場合でも柔軟に対応できます。
基本的には@param
タグを引数として、@return
タグを返り値として解釈し、メソッドの型定義を生成しています。
また、@yieldparam
タグと@yieldreturn
タグにも対応し、ブロックの型表現も可能です。
これ(Rubyコード)から
class Foo # @param [String] a # @return [Integer] # @yieldparam [Symbol] b # @yieldreturn [Boolean] def foo(a) end end
これ(RBS)を作れます。
class Foo # @param [String] a # @return [Integer] # @yieldparam [Symbol] b # @yieldreturn [Boolean] def foo(String a) { (Symbol b) -> bool } -> Integer end
orthosesは柔軟なので、アプリケーションだけでなくgemのRBSもorthoses-yardを使って生成できます。手始めにyard gem自体の型定義をYARDタグを元に生成してみました。yard gemならYARD記法を使いこなして広いパターンでの挙動確認ができそうだったのが選択理由です。
Add Yard gem by ksss · Pull Request #217 · ruby/gem_rbs_collection · GitHub
既存のYARDドキュメントを元にメソッドや定数の型を決定することができています。
RBS自動生成の肝
RBS自動生成最大の問題はmethodの型定義です。
orthosesによるRBS自動生成では、classの名前や継承関係、includeしているmoduleを自動的に洗い出したりするのは得意ですが、 methodの型定義を決めることは困難なのです。
なぜならmethodの型は基本的には人間が決めるべきであり、人間が書くべきです。 正しいものが何なのかわからないと、methodの実装自体が型チェックができないですしね。
def foo if cond "foo" else :bar end end
上記例だと静的解析すればfoo: () -> String | Symbol
となりますが、
実際はString
だけを期待していてSymbolが返るのは誤りになるケースも多くあるでしょう。
ましてやattr_accessor
だとさらに難しくなります。
methodの型は手で書くしか無いのでしょうか……。
でも自動化したいですよね。
もし既存の資産であるYARDドキュメントを利用できれば、methodやattr_accessorであっても型ファイルを生成できます。
このYARDからの生成によって、このmethod定義問題に良いアプローチができるんじゃないかと期待しています。
YARDを書けばRBSを生成できるならYARDを書くモチベーションを上げることもできますし、YARDとRBSで二重管理になることも防げます。
補足
ちなみにRubyの型界隈に詳しい人は、似たようなツールとしてsordを思い浮かべたと思います。 確かに今回実装したものはsordによく似ていますが、orthosesの拡張なので他のorthosesの機能と組み合わせられるという点が大きなウリとなっています。 例えば「Railsプロジェクトで一部だけYARDを使っている」という場合でも、orthoses-railsのrails拡張と組み合わせてプロジェクト全体の型定義をコントロール可能です。
YARDの限界
YARDは非常に歴史あるプロダクトであるため、途中から追加されたkeyword argumentsの対応が弱いように感じます。YARDタグだけではそれがキーワード引数なのかどうか判別できません。もちろんYARD的には問題ないのですが、型定義を生成する場合は情報が足りません。
# どっちも同じ書き方 # @param [String] key def bar(key) end # @param [String] key def foo(key:) end
orthoses-yardではMethodオブジェクトからparametersをとってYARD記法とマッチングさせることによってこの問題を解決しています。*2 また、RBSでのoverloadが表現できないことも若干ネックかなと思います。
# YARDだと表現できない def foo: (Integer) -> Integer | (String) -> String
これから
大まかな機能はできましたが、まだまだ細かいTODOが残っているので機能を拡充してプロダクトで使ってみて更に磨きをかけていこうかと思います。