LoginSignup
11
1

More than 1 year has passed since last update.

Rubyのprependによる定数探索バグ(3.1で修正済)

Last updated at Posted at 2022-01-14

最初に

Ruby技術者認定試験の対象バージョンは、いまだに2.1である。1

2.1にバグがあり、
Ruby技術者認定試験の有名なRExという練習問題で、
そのバグが混在されたまま出題され、
勉強中の受験生を悩ませている定数探索の問題がある。
実際、ruby-jpというSlackで、同じ質問が3回されているのを見た。

そのバグは3.1で修正されたのだが、そのバグについて解説する。
Ruby技術者認定試験Goldなので、けっこう難しめだと思う。

予備知識: 定数探索の順番

定数探索の順番

  1. レキシカルスコープ(Module.nesting)に則り、内側から外側のスコープへと探していく。
  2. 継承順(Module#ancestors)で、親クラスを順に探していく。
  3. トップレベル

ここの2番目でバグがありました。

本題: prependによる定数探索バグ(3.1で修正済)

下記のChildクラス内で定義されたメソッドで参照されるCONST定数がある。
まず、レキシカルスコープで、Child内のCONST定数を探すが、ChildCONST定数は定義されていない。
また、他のレキシカルスコープも存在しないので、継承順に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の公式の変更のまとめ等でとりわけ紹介はされてない。

  1. 2022年10月3日より、試験バージョンはRuby3.xになりました。

11
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
1