🎃

[Sorbet] mixes_in_class_methodsでmoduleがクラスに生やしたメソッドを認識させる

2025/02/25に公開

Rubyでは、あるmoduleがクラスからincludeされた場合に、クラス側にmodule側で定義したメソッドを生やすというパターンがあります。
今回は、Sorbetのmixes_in_class_methodsという機能を使って、メソッドの存在を型の上で知らせる方法について紹介します。

問題

以下のようにmodule Mでfooというメソッドを定義し、includeしたクラスにfooメソッドを生やしているとします。
普通に実行すれば、p A.foo"foo"という文字列が出力される正しいコードです。

module M
  module ClassMethods
    def foo = 'foo'
  end

  def self.included(other)
    other.extend(ClassMethods)
  end
end

class A
  include M
end

p A.foo # Method foo does not exist on T.class_of(A)

しかし、Sorbetはmodule MがAにメソッドを生やしていることを知らないため、A.fooにアクセスした行でMethod foo does not exist on T.class_of(A)という型エラーを出します。
include Mをしたときに、module ClassMethods経由でメソッドを生やしていることを示さなければいけません。

解決方法

そこで、mixes_in_class_methods使って、このmoduleをincludeしたクラスにClassMethods内で定義されたクラスメソッドを型として生やすようにします。

module M
  extend T::Helpers

  module ClassMethods
    def foo = 'foo'
  end

  def self.included(other)
    other.extend(ClassMethods)
  end

  mixes_in_class_methods(ClassMethods)
end

class A
  include M
end

p A.foo # -> "foo"

Sorbet Playground

mixes_in_class_methods(ClassMethods)と書くことで、include MしているクラスAにfooというメソッドが生えたことを型で保証できます。

Sorbetの公式ドキュメントではmixes_in_class_methods単体のページはありませんが、Abstract Classes and Interfacesのページで言及されています。

Discussion