Skip to content

Commit 0768f08

Browse files
flavorjonesk0kubun
authored andcommitted
Fix UnboundMethod#== for methods from included/extended modules [Backport #21873]
Method#unbind clones the method entry, preserving its defined_class. For methods mixed in via include/extend, defined_class is an ICLASS, causing UnboundMethod#== to return false when comparing against the same method obtained via Module#instance_method. Resolve ICLASS defined_class in method_eq. [Bug #21873]
1 parent e025c83 commit 0768f08

4 files changed

Lines changed: 62 additions & 0 deletions

File tree

proc.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,6 +1996,8 @@ method_eq(VALUE method, VALUE other)
19961996

19971997
klass1 = method_entry_defined_class(m1->me);
19981998
klass2 = method_entry_defined_class(m2->me);
1999+
if (RB_TYPE_P(klass1, T_ICLASS)) klass1 = RBASIC_CLASS(klass1);
2000+
if (RB_TYPE_P(klass2, T_ICLASS)) klass2 = RBASIC_CLASS(klass2);
19992001

20002002
if (!rb_method_entry_eq(m1->me, m2->me) ||
20012003
klass1 != klass2 ||

spec/ruby/core/unboundmethod/equal_value_spec.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@
3535

3636
@method_one = UnboundMethodSpecs::Methods.instance_method(:one)
3737
@method_two = UnboundMethodSpecs::Methods.instance_method(:two)
38+
39+
@mixin = UnboundMethodSpecs::Mixin.instance_method(:mixin_method)
40+
@includer_base = UnboundMethodSpecs::IncluderBase.new.method(:mixin_method).unbind
41+
@includer_child = UnboundMethodSpecs::IncluderChild.new.method(:mixin_method).unbind
42+
@extender_base = UnboundMethodSpecs::ExtenderBase.method(:mixin_method).unbind
43+
@extender_child = UnboundMethodSpecs::ExtenderChild.method(:mixin_method).unbind
3844
end
3945

4046
it "returns true if objects refer to the same method" do
@@ -91,6 +97,30 @@
9197
(@includer == @includee).should == true
9298
end
9399

100+
ruby_bug "#21873", ""..."4.0" do
101+
it "returns true if same method is present in an object through module inclusion" do
102+
(@mixin == @includer_base).should == true
103+
(@includer_base == @mixin).should == true
104+
105+
(@mixin == @includer_child).should == true
106+
(@includer_child == @mixin).should == true
107+
108+
(@includer_base == @includer_child).should == true
109+
(@includer_child == @includer_base).should == true
110+
end
111+
112+
it "returns true if same method is present in an object through module extension" do
113+
(@mixin == @extender_base).should == true
114+
(@extender_base == @mixin).should == true
115+
116+
(@mixin == @extender_child).should == true
117+
(@extender_child == @mixin).should == true
118+
119+
(@extender_base == @extender_child).should == true
120+
(@extender_child == @extender_base).should == true
121+
end
122+
end
123+
94124
it "returns false if both have same Module, same name, identical body but not the same" do
95125
class UnboundMethodSpecs::Methods
96126
def discard_1; :discard; end

spec/ruby/core/unboundmethod/fixtures/classes.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ class << self
8080
end
8181
end
8282

83+
module Mixin
84+
def mixin_method; end
85+
end
86+
87+
class IncluderBase
88+
include Mixin
89+
end
90+
91+
class IncluderChild < IncluderBase; end
92+
93+
class ExtenderBase
94+
extend Mixin
95+
end
96+
97+
class ExtenderChild < ExtenderBase; end
98+
8399
class A
84100
def baz(a, b)
85101
return [__FILE__, self.class]

test/ruby/test_method.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,20 @@ def foo() :derived; end
111111
end
112112
end
113113

114+
def test_unbound_method_equality_with_extended_module
115+
m = Module.new { def hello; "hello"; end }
116+
base = Class.new { extend m }
117+
sub = Class.new(base)
118+
119+
from_module = m.instance_method(:hello)
120+
from_base = base.method(:hello).unbind
121+
from_sub = sub.method(:hello).unbind
122+
123+
assert_equal(from_module, from_base)
124+
assert_equal(from_module, from_sub)
125+
assert_equal(from_base, from_sub)
126+
end
127+
114128
def test_callee
115129
assert_equal(:test_callee, __method__)
116130
assert_equal(:m, Class.new {def m; __method__; end}.new.m)

0 commit comments

Comments
 (0)