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