🍣

[Bug #19749] define_method で既存のメソッドを再定義したときの可視化性に関するチケット

2024/06/07に公開

[Bug #19749] Confirm correct behaviour when attaching private method with #define_method

  • 次のように private メソッドを #define_method 経由で呼び出した時にエラーにならないのは期待する挙動かどうかの確認のチケット
# これは private メソッドとして定義される
def bar; end


foo = Object.new
# private メソッドを public メソッドとして定義する
foo.singleton_class.send(:define_method, :bar, method(:bar))

# これはエラーになるのかならないのか
foo.bar # No error.
  • 起因としては TruffleRuby だと上のコードがエラーになるらしい
  • これは #define_method すると新しいメソッド定義しているだけなので可視化性は現在のスコープに依存するとのこと
    • なので次のような定義だと private になる
def bar; end

foo = Object.new
foo.singleton_class.class_exec do
  private
  foo.singleton_class.send(:define_method, :bar, method(:bar))
end

# error: private method `bar' called for #<Object:0x000077436c728fb8> (NoMethodError)
foo.bar # NoMethodError
  • 起票されていた内容自体は問題ないが、チケットの中で以下のような挙動が見つかる
class A
  def a; end
  private def b; end

  m = instance_method(:b)
  define_method(:c, m) # public メソッドとして定義される
  define_method(:b, m) # これも public メソッドとして定義されることを期待するが private メソッドになる

  private

  m = instance_method(:a)
  define_method(:d, m) # private メソッドとして定義される
  define_method(:a, m) # これも private メソッドとして定義されることを期待するが public メソッドになる

  # [:c, :b] を期待するが [:c, :a] が返ってくる
  p public_instance_methods(false)  # => [:c, :a] # should be [:c, :b]

  # [:a, :d] を期待するが [:b, :d] が返ってくる
  p private_instance_methods(false) # => [:b, :d] # should be [:a, :d]
end
  • 要するに同名で define_method(:foo, instance_method(:foo)) を定義するときに可視化性も更新するかどうか、ってところがポイントですね
  • 1年近く前のチケットだったんですが最終的には『可視化性を更新する』という形になったみたいです
class C
  def foo; end

  private

  define_method(:foo, instance_method(:foo))
  p private_instance_methods(false)
  # Ruby 3.3 => []
  # Ruby 3.4 => [:foo]
end
GitHubで編集を提案

Discussion