【一人 bugs.ruby Advent Calendar 2021】[Feature #11689] Add methods allow us to get visibility from Method and UnboundMethod object.【17日目】

一人 bugs.ruby Advent Calendar 2021 17日目の記事になります。
今回はメソッドオブジェクトに対してアクセシビリティの情報を取得するメソッドの話です。

[Feature #11689] Add methods allow us to get visibility from Method and UnboundMethod object.

メソッドのアクセシビリティを返す Method#visibility UnboundMethod#visibility を追加する提案です。
Method#visibilityアクセシビリティに応じて :public :protected :private のいずれかが返ってくる想定です。
ユースケースとしては任意のメソッドをラップする場合に元のメソッドと合わせるために使用することができます。
具体的にはこういうケース や以下のケースなど。

class Object
  def debugging(name)
    original = instance_method(name)

    # 提案
    # visibility が `:public` `:protected` `:private` を返す
    method_visibility = original.visibility
    # 既存の実装だとこう書く必要がある
    # method_visibility = if private_method_defined?(name)
    #                       :private
    #                     elsif protected_method_defined?(name)
    #                       :protected
    #                     else
    #                       :public
    #                     end
    define_method(name) { |*args, &block|
      pp name
      original.bind(self).call(*args, &block)
    }

    # 元のメソッドに合わせる
    send(method_visibility, name)
  end
end

class X
  def hoge
    pp 1 + 2
  end
  private :hoge

  debugging :hoge
end

# error: private method `hoge' called for #<X:0x000055f0b1abf3e0> (NoMethodError)
X.new.hoge

このチケットでは色々と議論が進んでいたんですが最終的には元々の提案の #visibility は入りませんでした。
その代わりとして {Method,UnboundMethod}#{public?,private?,protected?} のようにアクセシビリティを判定するメソッドが追加されました。

def hoge
end

pp method(:hoge).private?  # => true
pp method(:hoge).public?   # => false

public :hoge

pp method(:hoge).private?  # => false
pp method(:hoge).public?   # => true

最初に提示されたユースケースだとこんな感じで利用することができます。

class Object
  def debugging(name)
    original = instance_method(name)

    # 既存の実装だとこう書く必要がある
    method_visibility = if original.private?
                          :private
                        elsif original.protected?
                          :protected
                        else
                          :public
                        end
    define_method(name) { |*args, &block|
      pp name
      original.bind(self).call(*args, &block)
    }

    # 元のメソッドに合わせる
    send(method_visibility, name)
  end
end

class X
  def hoge
    pp 1 + 2
  end
  private :hoge

  debugging :hoge
end

# error: private method `hoge' called for #<X:0x000055f0b1abf3e0> (NoMethodError)
X.new.hoge

どちらかと判定メソッドが必要なケースが多そうなのでこっちのほうが汎用性は高そうですね。