最初に
Ruby技術者認定試験の対象バージョンは、いまだに2.1である。1
2.1にバグがあり、
Ruby技術者認定試験の有名なRExという練習問題で、
そのバグが混在されたまま出題され、
勉強中の受験生を悩ませている定数探索の問題がある。
実際、ruby-jpというSlackで、同じ質問が3回されているのを見た。
そのバグは3.1で修正されたのだが、そのバグについて解説する。
Ruby技術者認定試験Goldなので、けっこう難しめだと思う。
予備知識: 定数探索の順番
定数探索の順番
- レキシカルスコープ(
Module.nesting
)に則り、内側から外側のスコープへと探していく。 - 継承順(
Module#ancestors
)で、親クラスを順に探していく。 - トップレベル
ここの2番目でバグがありました。
本題: prependによる定数探索バグ(3.1で修正済)
下記のChild
クラス内で定義されたメソッドで参照されるCONST
定数がある。
まず、レキシカルスコープで、Child
内のCONST
定数を探すが、Child
にCONST
定数は定義されていない。
また、他のレキシカルスコープも存在しないので、継承順にCONST
定数を探索していくことになる。
module PrependedModule
CONST = "PrependedModule"
end
class Parent
CONST = "Parent"
prepend PrependedModule
end
class Child < Parent
p Module.nesting #=> [Child]
# レキシカルスコープは、Childのみ。
def self.const
CONST
end
end
p Child.ancestors #=> [Child, PrependedModule, Parent, Object, Kernel, BasicObject]
p Child.const
#=> "Parent" (誤: Ruby 3.0)
#=> "PrependedModule" (正: Ruby 3.1)
レキシカルスコープ上に定数がないので、継承順に定数を探索していくことになる。
prepend
で継承したモジュールは、そのクラスよりも優先度が高い。
実際、ancestors
メソッドで確かめると、Parent
クラスよりもPrependedMoudle
モジュールの方が優先順位が高くなっている。
そのため、PrependedModule
内にある定数を探索すべきだが、3.0まではParent
クラスにあるCONST
定数を先に見つけてしまっていた。これが3.1で修正されPrependedModule
の方が先に見つかるようになっている。
なお、これは(マニアックな)バグの修正という扱いで、Ruby 3.1の公式の変更のまとめ等でとりわけ紹介はされてない。
-
2022年10月3日より、試験バージョンはRuby3.xになりました。 ↩