RBS::Inline を導入してみました (1) | Webシステム開発/教育ソリューションのタイムインターメディア (original) (raw)

RubyKaigi 2024 の Embedding it into Ruby code というトークで RBS::Inline が発表されてから、4ヶ月が経ちました。

その間にいくつかの導入事例、利用記事を見かけてきましたが、タイミーさんの 前編:YARD から rbs-inline に移行しました という記事を読んで、重い腰を上げて RBS::Inline を導入することにしました。

この記事では、RBS::Inline を導入するにあたってハマったところや工夫が必要だった箇所について紹介します。

通常の導入や文法についての説明は省きますので、そのあたりは 公式の Syntax guideやタイミーさんの記事を見てください。

どの構文を選ぶのか

RBS::Inline ではコメント形式で型情報を記述します。

attr_* アクセサの型や定数の型、メソッドの型など、さまざまなものに型コメントをつけることができるのですが、
このうち、メソッドの型の記述には 3種類のコメント形式が提供されています。

※ RBS::Inline が公開された 5月の時点では、コメント形式はまだ決定していないというお話でした。先日の rbs-inlineを導入してYARDからRBSに移行する のトークで、構文が確定したという宣言がありました。

# @rbs name: String -- 氏名
# @rbs age: Integer -- 年齢
# @rbs return: String -- あいさつを表す文字列
def hello(name, age)
  ...
end
#: (String name, Integer age) -> String
def hello(name, age)
  ...
end
def hello(name, age) #: String
  ...
end

3つの構文のうち、どれを使って型コメントを書いていくのか、一度チームメンバーと相談してみました。
その際には以下のような意見が出ました。

しばらく議論をしましたが、これといった決め手がなかったこともあり、先行して RBS::Inline を触っていた僕の好みをベースに、以下のルールで進めることになりました。

しばらくこのポリシーで書き進めてみて、もう少し経験を積んでから、どの形式が良いのか改めて検討してみたいと思います。

Rubocop とのコンフリクト

RBS::Inline の型コメントでは、 #: という形式のコメントをよく利用します。

attr_* アクセサや定数に型を付ける際に使えますし、メソッドの型を記述する際にも使えます。

MAX_COUNT = 100 #: Integer

attr_reader :name #: String

#: () -> void
def say_hello
  puts "hello"
end

しかし、この #: 形式のコメントは 3つの Rubocop のルール(cop)とバッティングすることがわかっています。

これらの指摘の回避方法は現状では存在しません。そのため、.rubocop.yml でこれらの cop を無効化することにしました。

# for RBS::Inline
Style/AccessorGrouping:
    Enabled: false

Style/CommentedKeyword:
    Enabled: false

Layout/LeadingCommentSpace:
    Enabled: false

しかし、このアプローチはあまり好ましくないですよね。僕らは型コメントを書きたいのであって、変なコメントは検出したいのです。この設定ではせっかくの cop が有効活用できません。

ということで、Rubocop に対して RBS::Inline の型コメントを受け入れるよう変更を提案してみました。

その結果、これらは本体にマージされることになりました。10/4 現在、まだリリースはされていないのですが、次のリリース以降では設定をひとつ追加するだけで RBS::Inline の型コメントが利用できます。

Layout/LeadingCommentSpace:
    AllowRBSInlineAnnotation: true

リリースが待ち遠しいですね。

10/17追記: この修正を含んだ 1.67.0 がリリースされていました🎉

module-self-types に複数のモジュールが書けない問題

手元には concern や helper など、module-self-types に複数のモジュールが指定されているモジュールがありました。(参考: module-self-types については RBS の self-type を理解する をお読みください。)

module ApplicationHelper  : ActionController::Base, ActionView::Base
  ...
end

しかし、これを RBS::Inline の # @rbs module-self という型コメントで記述しても、先頭のモジュールしか認識されません。これはバグっていますね…

# @rbs module-self ApplicationController::Base, ActionView::Base
module Kernel
  # => ApplicationController::Base だけが認識される
end

こういうときは、フットワーク軽くバグを直しましょう。

ということで、RBS::Inline にPR を送ってみたところ、すぐにマージしていただきました。RBS::Inline 0.8.0 ではこの問題が解消されており、複数の module-self-types が記述できるようになっています。

まとめ

ここまでくれば、あとはひたすら書き換えるだけですね。試しに導入した Rails アプリには 170ファイル強の型が存在していたのですが、もくもくと1日程度で書き換えが完了しました。実際には、小出しに PR を送り、型コメントの解説をしたり、ついでに既存の型を見直ししたり、寄り道をしながら反映をしていたので、すべての変更がマージされるまでには 3週間程度かかっています。

Rails そのものであるとか、ライブラリといった、アプリケーション以外のモジュールに対する型は .rbs ファイルに記述していますが、それ以外はすべて RBS::Inline に移行が完了しています。

この記事では RBS::Inline の導入にあたってハマったところを紹介しました。

これから RBS::Inline を試してみようという方の参考になれば幸いです。