pockestrap

Programmer's memo

RBSではRubyと定数探索がちょっとだけ違う

ほとんど一緒なので気にすることはあまりないとは思います。


タイトル通り、RBSRubyでは定数探索がちょっとだけ違います。 具体的にはスーパークラスinclude元のモジュールで定義された定数がRBSでは参照できません。

Rubyの場合

まずはRubyの場合の挙動を確認しましょう。 次のRubyのコードを題材にします。

# Ruby

module M
  class A
  end
end

class P
  class B
  end
end

module N
  class C < P
    include M

    def foo
      A.new
    end

    def bar
      B.new
    end
  end
end

obj = N::C.new

p obj.foo # => #<M::A:0x0000564e4c1cb178>
p obj.bar # => #<P::B:0x0000564e4c1cafc0>

このコードの実行結果からわかるとおり、Rubyではスーパークラスinclude元のモジュールで定義された定数を、その子クラスから参照できます。 つまり、N::Cの中でAを参照すると、それはN::CincludeしているモジュールMで定義されたM::Aを指します。 またN::Cの中でBを参照すると、N::CスーパークラスPの中で定義されたP::Bを指します。

RBSの場合

ところがRBSの場合にはこの挙動が違います。 RBSではスーパークラスinclude元で定義された定数を参照できません。

先程のRubyコードに対応するRBSは次のようになります。

# RBS

module M
  class A
  end
end

class P
  class B
  end
end

module N
  class C < P
    include M

    # def foo: () -> A だとAを参照できないため、
    # M::Aのようにフルネームを書く必要がある
    def foo: () -> M::A

    # def bar: () -> B だとBを参照できない
    # P::Bのようにフルネームを書く必要がある
    def bar: () -> P::B
  end
end

Rubyのコードを見ながら型定義を書いている場合や、rbs prototype rbを使って型定義を生成している場合、この問題にハマることがあるかもしれません。 その時は正しい型が参照できるよう、型名の参照をより詳しい名前で書くと良いでしょう。

ちなみにこの挙動以外はだいたいRubyRBSで定数探索は同じような挙動をすると思います。


参考

Type name resolution became more compatible with Ruby's constant resolution. It resolves given relative type names based on class/module nesting structure. Note that the type name resolution does not refer inheritance and includes.

https://github.com/ruby/rbs/pull/307