RBSというRubyの型のためのソフトウェアがあり、RBSはrbs
コマンドを含んでいる。
このコマンドは、.rbs
ファイル(Rubyの型定義ファイル)を書いていく上で必要になる。
この記事では、そのrbs
コマンドの各サブコマンドについて、簡単に解説する。
rbs
コマンドの実装は lib/ast/cli.rb に書かれている。
サブコマンドの実装は、 run_サブコマンド名
メソッドを読むと良い。つまり、rbs ast
コマンドの実装はrun_ast
メソッドにある。
現時点(2020-06-15)のmasterブランチの最新版( https://github.com/ruby/rbs/commit/00872ed5de364a5d2fcf8169aee87307ba67aad3 )を元にしている。
サブコマンドに共通のオプション
まず、共通で使えるオプションの説明をする。
サブコマンドに共通のオプションには以下がある。
--help
-r LIBRARY
-I DIR
--no-stdlib
--log-level=LEVEL
--log-output=OUTPUT
このうち、-r
, -I
, --no-stdlib
について説明する。
-r
これはruby
コマンドの-r
と同じで、ライブラリをrequireする。
つまり、-r pathname
などとするとpathnameの型がrequireされた上でrbsコマンドが実行される。
-I
これはruby
コマンドの-I
と似ていて、.rbs
ファイルを置いたディレクトリを指定する。
ruby
コマンドのそれとは異なり、-I
に指定したディレクトリ配下の.rbs
ファイルは全て読み込まれる。
つまり、-I .
とするとカレントディレクトリ配下の.rbs
ファイルを全て読み込んだ上でrbsコマンドが実行される。
--no-stdlib
標準ライブラリなしでrbsコマンドを実行する。つまり、ArrayもStringもIntegerもない世界でrbsコマンドが実行される。
以下で各サブコマンドの解説をする。
rbs prototype
既存の資産からrbsを生成する。rbs prototype rb
, rbs prototype runtime
, rbs prototype rbi
の3つがある。
実際に型を書いていく際には、このうちのどれかを使って雛形を生成した上で、それに手を加えていく形になると思う。
このうちrbi
は使ったことがないので、それ以外の2つを解説する。
rbs prototype rb
.rb
ファイルを静的に解析して.rbs
ファイルを生成する。
# test.rb class C attr_accessor :foo def bar(x, y, z) [x, y, z] end eval <<~RUBY def baz :baz end RUBY end
$ rbs prototype rb test.rb class C attr_accessor foo: untyped def bar: (untyped x, untyped y, untyped z) -> ::Array[untyped] end
静的に解析するので、eval
の中で定義されているbaz
メソッドは取れない。
一方、foo
アトリビュートやbar
メソッドの定義は出力されている。
rbs prototype rb
は戻り値がリテラルであればそれっぽい戻り値の型を出力するようになっている。
例えばbar
メソッドはArrayを返すことが分かるので、そのように出力されている。
これは私が実装した。
rbs prototype runtime
prototype rb
とは違い、動的に.rb
ファイルを読み込んで.rbs
ファイルを生成する。
prototype rb
と同じtest.rb
に対して実行すると次のようになる。
$ rbs prototype runtime --require-relative test.rb C class C public def bar: (untyped x, untyped y, untyped z) -> untyped def baz: () -> untyped def foo: () -> untyped def foo=: (untyped) -> untyped end
このように--require
もしくは--require-relative
オプションでファイルを読み込んだ上で実行する。
(ちなみに、railsアプリケーションの場合はconfig/environment.rb
を読み込んだ上でeager_loadしてやるとうまくいくよう。)
prototype rb
の場合とは異なり、eval
で定義されているbaz
メソッドの情報も取れている。
一方、foo
アトリビュートが単なるメソッドとして定義されていたり、bar
メソッドの戻り値がuntyped
になってしまったりしている。
このようにrb
とruntime
は一長一短なので、適当に使い分けると良いと思う。
runtime
は動的に定義されたメソッドを拾える特徴があるので、メタプロをゴリゴリ使っている場合はそちらを使うと良さそう。
一方runtime
はrb
に比べてまともに動かすのがめんどい。rb
は静的な解析しかしないので詰まることはあまりないのだけど、runtime
はコードを実行しなければいけないので、ハマりどころは沢山ある。
たとえばautoload
で定義された定数は何もしなければ無なので、適当に読み込んでやる必要があったりする(これはrbsを直したほうが良いかも)。
rbs validate
.rbs
ファイルの定義が壊れていないかを検証する。
なので、.rbs
ファイルを書くときはこれを実行しながら書いていくと良さそう。
例えばsig/
ディレクトリ下にrbsを書いているときは次のように実行する。
$ rbs -I sig/ validate
rbs parse
与えられた.rbs
ファイルをパースした上で、syntax errorがあればそれを出力してexit 1する。
rbs validate
よりもやっていることが少ない分速そう。
# syntax-error.rbs class Foo # barの後には : が必要 def bar () -> untyped end
$ rbs parse syntax-error.rbs syntax-error.rbs:5:10: parse error on value: (kLPAREN) $ echo $? 1
rbs ast
読み込まれているrbsファイルのASTを全て表示する。つまり、--no-stdlib
オプションをつけないと標準ライブラリのASTも表示されて不便。
たとえばカレントディレクトリに以下の.rbs
ファイルを置いて実行すると次の出力が得られる。
# test.rbs class Foo def bar: () -> untyped end
$ rbs -I . --no-stdlib ast [{"declaration":"class","name":"Foo","type_params":{"params":[]},"members":[{"member":"method_definition","kind":"instance","types":[{"type_params":[],"type":{"required_positionals":[],"optional_positionals":[],"rest_positionals":null,"trailing_positionals":[],"required_keywords":{},"optional_keywords":{},"rest_keywords":null,"return_type":{"class":"untyped","location":{"start":{"line":2,"column":17},"end":{"line":2,"column":24},"buffer":{"name":"test.rbs"}}}},"block":null,"location":{"start":{"line":2,"column":11},"end":{"line":2,"column":24},"buffer":{"name":"test.rbs"}}}],"annotations":[],"location":{"start":{"line":2,"column":2},"end":{"line":2,"column":24},"buffer":{"name":"test.rbs"}},"comment":null,"attributes":[]}],"super_class":null,"annotations":[],"location":{"start":{"line":1,"column":0},"end":{"line":3,"column":3},"buffer":{"name":"test.rbs"}},"comment":null}]
rbs list
定義されているクラス、モジュール、インターフェイスの一覧を出力する。
--class
, --module
, --interface
オプションを指定すると、指定したものだけが出力される。
rbs ancestors
クラスを指定すると、その祖先を順に表示する。
$ rbs ancestors String ::String ::Comparable ::Object ::Kernel ::BasicObject
rubyのコードだと、Module#ancestors
に対応する。
rbs methods
指定したクラスに存在するメソッドの一覧を返す。
$ rbs methods String (メソッドの一覧が出てくる)
rubyのコードだと、Module#instance_methods
やObject#methods
などに対応する。
rbs method
メソッドの型情報を表示する。
$ rbs method String gsub ::String#gsub defined_in: ::String implementation: ::String accessibility: public types: (::Regexp | ::string pattern, ::string replacement) -> ::String | (::Regexp | ::string pattern, ::Hash[::String, ::String] hash) -> ::String | (::Regexp | ::string pattern) { (::String match) -> ::_ToS } -> ::String | (::Regexp | ::string pattern) -> ::Enumerator[::String, self]
rbs constant
定数を表示する。
$ rbs constant RUBY_VERSION Context: :: Constant name: RUBY_VERSION => ::RUBY_VERSION: ::String
rbs paths
読み込まれているpathを表示するっぽい? 使わないのでよくわからない。
rbs vendor
わからん。
rbs versuion
バージョン番号が出る。
$ rbs version rbs 0.3.1