タイトルを読んでも何を言っているのかわからないと思いますが、モジュールをincludeしようとするとRBS::RecursiveAncestorErrorが発生することがあります。
とくにObjectクラスにincludeするようなケースでは注意が必要です。
この記事ではrbs gem v1.0.0で動作確認しています。
Problem
次のtest.rbsを用意します。このRBSでは、Mモジュールを定義し、それをObjectクラスにincludeしているだけです。
# test.rbs module M end class Object include M end
このtest.rbsを置いたディレクトリでrbs validateを実行すると、次のエラーが発生してしまいます。
$ rbs -I . validate --silent
/path/to/lib/rbs/errors.rb:91:in `check!': /path/to/core/object.rbs:16:0...818:3: Detected recursive ancestors: ::Array[Elem] < ::Object < ::M < ::Object (RBS::RecursiveAncestorError)
from /path/to/lib/rbs/definition_builder/ancestor_builder.rb:337:in `instance_ancestors'
from /path/to/lib/rbs/definition_builder/ancestor_builder.rb:362:in `block in instance_ancestors'
from /path/to/lib/rbs/definition_builder/ancestor_builder.rb:359:in `each'
from /path/to/lib/rbs/definition_builder/ancestor_builder.rb:359:in `instance_ancestors'
from /path/to/lib/rbs/definition_builder/ancestor_builder.rb:371:in `block in instance_ancestors'
from /path/to/lib/rbs/definition_builder/ancestor_builder.rb:368:in `each'
from /path/to/lib/rbs/definition_builder/ancestor_builder.rb:368:in `instance_ancestors'
from /path/to/lib/rbs/definition_builder/ancestor_builder.rb:353:in `instance_ancestors'
from /path/to/lib/rbs/definition_builder.rb:132:in `block in build_instance'
from /path/to/lib/rbs/definition_builder.rb:731:in `try_cache'
from /path/to/lib/rbs/definition_builder.rb:126:in `build_instance'
from /path/to/lib/rbs/cli.rb:423:in `block in run_validate'
from /path/to/lib/rbs/cli.rb:421:in `each_key'
from /path/to/lib/rbs/cli.rb:421:in `run_validate'
from /path/to/lib/rbs/cli.rb:113:in `run'
from /path/to/exe/rbs:7:in `<top (required)>'
from /home/pocke/.rbenv/versions/trunk/bin/rbs:23:in `load'
from /home/pocke/.rbenv/versions/trunk/bin/rbs:23:in `<main>'
また次のような状態でも同様のエラーが発生します。
RBSのモジュール定義ではモジュール名の後にコロンを書いて、「そのモジュールがincludeされる先のインターフェイス」を指定できます。ところが、そこにinclude先のクラスを直接指定はできません。
module M2 : C end class C include M2 end
$ rbs -I . validate --silent
/path/to/rbs-1.0.0/lib/rbs/errors.rb:63:in `check!': test.rbs:2:0...3:3: Detected recursive ancestors: ::M2 < ::C < ::M2 (RBS::RecursiveAncestorError)
from /path/to/rbs-1.0.0/lib/rbs/definition_builder/ancestor_builder.rb:385:in `instance_ancestors'
from /path/to/rbs-1.0.0/lib/rbs/definition_builder/ancestor_builder.rb:419:in `block in instance_ancestors'
from /path/to/rbs-1.0.0/lib/rbs/definition_builder/ancestor_builder.rb:416:in `each'
from /path/to/rbs-1.0.0/lib/rbs/definition_builder/ancestor_builder.rb:416:in `instance_ancestors'
from /path/to/rbs-1.0.0/lib/rbs/definition_builder/ancestor_builder.rb:410:in `block in instance_ancestors'
from /path/to/rbs-1.0.0/lib/rbs/definition_builder/ancestor_builder.rb:407:in `each'
from /path/to/rbs-1.0.0/lib/rbs/definition_builder/ancestor_builder.rb:407:in `instance_ancestors'
from /path/to/rbs-1.0.0/lib/rbs/definition_builder.rb:141:in `block in build_instance'
from /path/to/rbs-1.0.0/lib/rbs/definition_builder.rb:765:in `try_cache'
from /path/to/rbs-1.0.0/lib/rbs/definition_builder.rb:135:in `build_instance'
from /path/to/rbs-1.0.0/lib/rbs/cli.rb:423:in `block in run_validate'
from /path/to/rbs-1.0.0/lib/rbs/cli.rb:421:in `each_key'
from /path/to/rbs-1.0.0/lib/rbs/cli.rb:421:in `run_validate'
from /path/to/rbs-1.0.0/lib/rbs/cli.rb:113:in `run'
from /path/to/rbs-1.0.0/exe/rbs:7:in `<top (required)>'
from /home/pocke/.rbenv/versions/trunk/bin/rbs:23:in `load'
from /home/pocke/.rbenv/versions/trunk/bin/rbs:23:in `<main>'
Cause
module M endはmodule M : Object endのshorthandです。
このshorthandを解決すると、この2つのコードは同じ形になります。そのため、この2つのエラーの原因は同じです。
では、このコロンはどういう意味を表しているのでしょうか?
module M : Object endと定義したモジュールMは、Object相当のクラスにincludeできます。
言い換えると、モジュールMの中ではObjectに定義されたメソッド(例えばObject#tap)を使えます。
module M2 :C endも同様で、このモジュールM2はC相当のクラスにincludeできます。
ではなぜObjectクラスにinclude Mするとエラーになるのでしょうか。include先として指定しているのだから、includeできると考えるのが直感的です。
この原因は、このままではObjectの定義が解決できないためです。
では、RBSの気持ちになってObjectの定義の解決を試みてみましょう。
この定義では、ObjectにはMモジュールがincludeされています。つまりObjectの定義を解決するには、先にMモジュールの定義を解決する必要があります。
ところで、Mモジュールの定義を解決するには、Objectの定義を解決する必要があります。なぜならば、MモジュールはObjectクラス相当にincludeできるモジュールであり、Objectクラスがどんなインターフェイスかを知る必要があるためです。
……と、ここでObjectとMの定義の解決でループができてしまいました。
今回のエラーはこのループを指摘するものです。つまり、無事ObjectクラスにincludeできるMモジュールを定義するには、このループを解決しなければなりません。
Solution
Mモジュールのinclude先に、Object以外を指定すれば良いです。
たとえばObjectの親クラスであるBasicObjectや、適当なインターフェイスを指定すると良いでしょう。
BasicObjectを指定する例。この例では、MモジュールからはBasicObjectが持つメソッドしか使用できません。
# test.rbs module M : BasicObject end class Object include M end
Mモジュールに本当に必要なインターフェイスだけを指定する例。
# Object#tap 相当のメソッドだけを持ったインターフェイス interface _Tappable def tap: () { (self) -> void } -> self end # モジュールMは _Tappable インターフェイスを # 満たすクラス/モジュールにならincludeできる module M : _Tappable end class Object include M end
まとめ
ちょっと分かりづらくて解決がむずいのではないかなと思って記事にしました。
Objectにincludeするモジュールはあまり書くことはないと思いますが、1つのクラスにしかincludeしないモジュールはRailsを書いていると見かけることがある(1つのモデルにしかincludeしないconcernとか)ので、Railsアプリケーションに型をつけているとこのエラーに出会うことがあるかもしれません。そのときにはこの記事を参考にして頂ければ幸いです。