From: "Eregon (Benoit Daloze)" Date: 2022-04-19T18:30:01+00:00 Subject: [ruby-core:108297] [Ruby master Bug#18729] Method#owner and UnboundMethod#owner are incorrect after using Module#public/protected/private Issue #18729 has been updated by Eregon (Benoit Daloze). This RSpec test: https://github.com/rspec/rspec-mocks/blob/1611752449b5beefa1939dd2a20beb0175d46110/spec/rspec/mocks/partial_double_spec.rb#L620 currently relies on the inconsistent behavior of `owner`, here: https://github.com/rspec/rspec-mocks/blob/1611752449b5beefa1939dd2a20beb0175d46110/lib/rspec/mocks/method_reference.rb#L192 The code wants to find out if `new` on a given class is the same definition as `Class#new`. The way used there with `owner` doesn't work through aliases, which already correctly sets the `owner`. There is currently no *easy* way to know if 2 Method or UnboundMethod "are the same code/definition". That's because Method#== and UnboundMethod#== both compare more than just the definition, but also the receiver (for Method) or where the method was fetched from (for UnboundMethod): https://github.com/ruby/ruby/blob/v3_0_2/proc.c#L1772-L1780 I think ideally that should change, or we should have a separate method to compare 2 Method/UnboundMethod to tell if they have the same definition. Exposing the `defined_class` (where the method definition was first used) as a new method wouldn't really help, because the method might have been redefined in between. However I found a way using `unbind` + `bind`: ```ruby class C class << self alias_method :n, :new private :new end end p C.method(:n) == C.method(:new) # => true p C.method(:n) == Class.method(:new) # => false p C.method(:n) == Class.method(:new).unbind.bind(C) # => true p C.method(:new) == Class.method(:new) # => false p C.method(:new) == Class.method(:new).unbind.bind(C) # => true ``` So that's what RSpec should use, I'll make a PR. ---------------------------------------- Bug #18729: Method#owner and UnboundMethod#owner are incorrect after using Module#public/protected/private https://bugs.ruby-lang.org/issues/18729#change-97317 * Author: Eregon (Benoit Daloze) * Status: Open * Priority: Normal * ruby -v: ruby 3.1.1p18 (2022-02-18 revision 53f5fc4236) [x86_64-linux] * Backport: 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN ---------------------------------------- The #owner should be "the class or module that defines the method". Or in other words, the owner is the module which has the method table containing that method. This generally holds, and it seems very likely this assumption is relied upon (e.g., when decorating a method, undefining it, etc). But the returned value on CRuby is incorrect for this case: ```ruby class A protected def foo :A end end class B < A p [instance_method(:foo), instance_method(:foo).owner, instance_methods(false), A.instance_methods(false)] public :foo p [instance_method(:foo), instance_method(:foo).owner, instance_methods(false), A.instance_methods(false)] end ``` It gives: ``` [#, A, [], [:foo]] [#, A, [:foo], [:foo]] ``` So `UnboundMethod#owner` says `A`, but clearly there is a :foo method entry in B created by `public :foo`, and that is shown through `B.instance_methods(false)`. The expected output is: ``` [#, A, [], [:foo]] [#, B, [:foo], [:foo]] ``` -- https://bugs.ruby-lang.org/ Unsubscribe: