OSS活動記 #6 - rbs_rails - prismパーサーを利用するようにする

対象リポジトリ

github.com

作成したPR

github.com

経緯

rbs_railsRailsアプリケーションでのrbsRubyの型定義ファイル)作成を支援してくれるもの。ActiveRecordモデルはDBカラムから動的にメソッド群が生える。そういった動的に生えるメソッドをケアしてくれるのがrbs_rails

RailsアプリでのSteep利用を改めて整理しようと思ってrbs_railsを実行したら以下のwarningが出ていた。

warning: parser/current is loading parser/ruby33, which recognizes 3.3.x-compliant syntax, but you are running 4.0.0.
Please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.

これrubocop-astで見たことあるやつだ!となったのでrbs_railsでもparser使っていることがすぐ検知できた。Ruby 4.0.0を使っているのだが、Ruby 3.4でもなくRuby 3.3系でパースされるのは心象良くないなと思ったのでコードを覗いてみた。

rbs_railsでparser使っている実装

以下でASTを取得しているのに利用している。

https://github.com/pocke/rbs_rails/blob/f1a7764617f57aac96e73d81fba1fedf5929bf0d/lib/rbs_rails/active_record.rb#L474

ASTからいくつかのメソッド呼び出しパターンを検知してrbsを追加する実装になっているようだ。そこらへんの詳細は主題から外れるのでこんなところでよし。

Parser::CurrentRuby とは

「parser」と書くと曖昧さがあるので「whitequark/parser」とする。そのgemの中に、実行時のRubyバージョンをチェックしてバージョン毎のパーサークラスを振り分けてくれるものが parser::CurrentRuby

https://github.com/whitequark/parser/blob/9520c3ac88f808595eea8f517c2eb271867f9a61/lib/parser/current.rb

冒頭のwarningsは warn_syntax_deviation メソッドで定義されているものだということもこの実装から確認できる。

PrismのTranslationレイヤー

Prismパーサーは既存パーサーとの互換性を持っておりI/FはそのままでPrismパーサーへと移行可能になっている。ここら辺の実装を把握&参考にさせてもらったのはrubocop-astでの実装なのでそのリンクを貼っておく。

https://github.com/rubocop/rubocop-ast/blob/69036498c11ca944c6099d1b672ba408f34a3eb4/lib/rubocop/ast/processed_source.rb#L260-L325

whitequark/parserの Parser::RubyXX クラスと Prism::Translation::ParserXX クラスとで互換性があることがこの実装からも確認できる。

Prismにも Prism::Translation::ParserCurrent があるけど使えるのは v1.5.0 から

以下で追加されている。

Add `Prism::Translation::ParserCurrent` · ruby/prism@77177f9 · GitHub

残念ながらrbs_railsのprismはv1.2.0でpinされてしまっている(rbs-inlineの依存がそうなっているため)

実装したバージョン分岐ロジック

rbs_railsRuby 3.2 未満はサポートしていないので気にしないくていいよ、とレビューコメントもらったので以下のようにした。

      private def parser_class #: untyped
        case RUBY_VERSION
        when /^3\.2\./
          # backward campatibility
          require 'parser/current'
          Parser::CurrentRuby
        when /^3\.3\./
          Prism::Translation::Parser33 # steep:ignore
        when /^3\.4\./
          Prism::Translation::Parser34 # steep:ignore
        else
          # For Prism v1.5.0+, Prism::Translation::ParserCurrent should be used instead.
          Prism::Translation::Parser34 # steep:ignore
        end
      end

Prismのバージョン上げたら Prism::Translation::ParserCurrent を利用するようにしたい。

まとめ

rubocop-astのソースコードリーディングしてparser周りの事情を少し把握できていたのでPR作成までスムーズにいけた。色々興味を持ってソースコードリーディングしておくものですね。