UnboundMethod only refer defined_class

UnboundMethod records caller's class, like `D` or `E` on the
following case:

```ruby
class C
  def foo = :foo
end

class D < C
end

class E < C
end

d = D.instance_method(:foo)
e = E.instance_method(:foo)
```

But `d` and `e` only refers `C#foo` so that UnboundMethod doesn't
record `D` or `E`. This behavior changes the following methods:

* `UnboundMethod#inspect` (doesn't show caller's class)
* `UnboundMethod#==` (`d == e` for example)

fix https://bugs.ruby-lang.org/issues/18798
This commit is contained in:
Koichi Sasada 2022-12-03 05:55:33 +09:00
parent 7161bf34e1
commit 59e389af28
Notes: git 2022-12-02 23:53:36 +00:00
6 changed files with 77 additions and 26 deletions

20
proc.c
View File

@ -1724,8 +1724,14 @@ mnew_internal(const rb_method_entry_t *me, VALUE klass, VALUE iclass,
method = TypedData_Make_Struct(mclass, struct METHOD, &method_data_type, data); method = TypedData_Make_Struct(mclass, struct METHOD, &method_data_type, data);
RB_OBJ_WRITE(method, &data->recv, obj); if (obj == Qundef) {
RB_OBJ_WRITE(method, &data->klass, klass); RB_OBJ_WRITE(method, &data->recv, Qundef);
RB_OBJ_WRITE(method, &data->klass, Qundef);
}
else {
RB_OBJ_WRITE(method, &data->recv, obj);
RB_OBJ_WRITE(method, &data->klass, klass);
}
RB_OBJ_WRITE(method, &data->iclass, iclass); RB_OBJ_WRITE(method, &data->iclass, iclass);
RB_OBJ_WRITE(method, &data->owner, original_me->owner); RB_OBJ_WRITE(method, &data->owner, original_me->owner);
RB_OBJ_WRITE(method, &data->me, me); RB_OBJ_WRITE(method, &data->me, me);
@ -1876,9 +1882,9 @@ method_unbind(VALUE obj)
method = TypedData_Make_Struct(rb_cUnboundMethod, struct METHOD, method = TypedData_Make_Struct(rb_cUnboundMethod, struct METHOD,
&method_data_type, data); &method_data_type, data);
RB_OBJ_WRITE(method, &data->recv, Qundef); RB_OBJ_WRITE(method, &data->recv, Qundef);
RB_OBJ_WRITE(method, &data->klass, orig->klass); RB_OBJ_WRITE(method, &data->klass, Qundef);
RB_OBJ_WRITE(method, &data->iclass, orig->iclass); RB_OBJ_WRITE(method, &data->iclass, orig->iclass);
RB_OBJ_WRITE(method, &data->owner, orig->owner); RB_OBJ_WRITE(method, &data->owner, orig->me->owner);
RB_OBJ_WRITE(method, &data->me, rb_method_entry_clone(orig->me)); RB_OBJ_WRITE(method, &data->me, rb_method_entry_clone(orig->me));
return method; return method;
@ -3139,7 +3145,11 @@ method_inspect(VALUE method)
defined_class = RBASIC_CLASS(defined_class); defined_class = RBASIC_CLASS(defined_class);
} }
if (FL_TEST(mklass, FL_SINGLETON)) { if (data->recv == Qundef) {
// UnboundMethod
rb_str_buf_append(str, rb_inspect(defined_class));
}
else if (FL_TEST(mklass, FL_SINGLETON)) {
VALUE v = rb_ivar_get(mklass, attached); VALUE v = rb_ivar_get(mklass, attached);
if (UNDEF_P(data->recv)) { if (UNDEF_P(data->recv)) {

View File

@ -27,8 +27,16 @@ describe "Method#unbind" do
@string.should =~ /MethodSpecs::MyMod/ @string.should =~ /MethodSpecs::MyMod/
end end
it "returns a String containing the Module the method is referenced from" do ruby_version_is ""..."3.2" do
@string.should =~ /MethodSpecs::MySub/ it "returns a String containing the Module the method is referenced from" do
@string.should =~ /MethodSpecs::MySub/
end
end
ruby_version_is "3.2" do
it "returns a String containing the Module the method is referenced from" do
@string.should =~ /MethodSpecs::MyMod/
end
end end
end end

View File

@ -45,10 +45,14 @@ describe "Module#instance_method" do
@parent_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/ @parent_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/
@child_um.inspect.should =~ /\bfoo\b/ @child_um.inspect.should =~ /\bfoo\b/
@child_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/ @child_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/
@child_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/
@mod_um.inspect.should =~ /\bbar\b/ @mod_um.inspect.should =~ /\bbar\b/
@mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethMod\b/ @mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethMod\b/
@mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/
ruby_version_is ""..."3.2" do
@child_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/
@mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/
end
end end
it "raises a TypeError if the given name is not a String/Symbol" do it "raises a TypeError if the given name is not a String/Symbol" do

View File

@ -76,19 +76,38 @@ describe "UnboundMethod#==" do
(@identical_body == @original_body).should == false (@identical_body == @original_body).should == false
end end
it "returns false if same method but one extracted from a subclass" do ruby_version_is ""..."3.2" do
(@parent == @child1).should == false it "returns false if same method but one extracted from a subclass" do
(@child1 == @parent).should == false (@parent == @child1).should == false
(@child1 == @parent).should == false
end
it "returns false if same method but extracted from two different subclasses" do
(@child2 == @child1).should == false
(@child1 == @child2).should == false
end
it "returns false if methods are the same but added from an included Module" do
(@includee == @includer).should == false
(@includer == @includee).should == false
end
end end
it "returns false if same method but extracted from two different subclasses" do ruby_version_is "3.2" do
(@child2 == @child1).should == false it "returns true if same method but one extracted from a subclass" do
(@child1 == @child2).should == false (@parent == @child1).should == true
end (@child1 == @parent).should == true
end
it "returns false if methods are the same but added from an included Module" do it "returns false if same method but extracted from two different subclasses" do
(@includee == @includer).should == false (@child2 == @child1).should == true
(@includer == @includee).should == false (@child1 == @child2).should == true
end
it "returns true if methods are the same but added from an included Module" do
(@includee == @includer).should == true
(@includer == @includee).should == true
end
end end
it "returns false if both have same Module, same name, identical body but not the same" do it "returns false if both have same Module, same name, identical body but not the same" do

View File

@ -20,12 +20,22 @@ describe :unboundmethod_to_s, shared: true do
it "the String shows the method name, Module defined in and Module extracted from" do it "the String shows the method name, Module defined in and Module extracted from" do
@from_module.send(@method).should =~ /\bfrom_mod\b/ @from_module.send(@method).should =~ /\bfrom_mod\b/
@from_module.send(@method).should =~ /\bUnboundMethodSpecs::Mod\b/ @from_module.send(@method).should =~ /\bUnboundMethodSpecs::Mod\b/
@from_method.send(@method).should =~ /\bUnboundMethodSpecs::Methods\b/
ruby_version_is ""..."3.2" do
@from_method.send(@method).should =~ /\bUnboundMethodSpecs::Methods\b/
end
end end
it "returns a String including all details" do it "returns a String including all details" do
@from_module.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Methods(UnboundMethodSpecs::Mod)#from_mod" ruby_version_is ""..."3.2" do
@from_method.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Methods(UnboundMethodSpecs::Mod)#from_mod" @from_module.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Methods(UnboundMethodSpecs::Mod)#from_mod"
@from_method.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Methods(UnboundMethodSpecs::Mod)#from_mod"
end
ruby_version_is "3.2" do
@from_module.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod"
@from_method.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod"
end
end end
it "does not show the defining module if it is the same as the origin" do it "does not show the defining module if it is the same as the origin" do

View File

@ -1236,12 +1236,12 @@ class TestMethod < Test::Unit::TestCase
unbound = b.instance_method(:foo) unbound = b.instance_method(:foo)
assert_equal unbound, b.public_instance_method(:foo) assert_equal unbound, b.public_instance_method(:foo)
assert_equal "#<UnboundMethod: B(A)#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
assert_equal [[:opt, :arg]], unbound.parameters assert_equal [[:opt, :arg]], unbound.parameters
a.remove_method(:foo) a.remove_method(:foo)
assert_equal "#<UnboundMethod: B(A)#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
assert_equal [[:opt, :arg]], unbound.parameters assert_equal [[:opt, :arg]], unbound.parameters
obj = b.new obj = b.new
@ -1281,7 +1281,7 @@ class TestMethod < Test::Unit::TestCase
a.remove_method(:foo) a.remove_method(:foo)
assert_equal "#<UnboundMethod: B(A)#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
assert_equal [[:opt, :arg]], unbound.parameters assert_equal [[:opt, :arg]], unbound.parameters
assert_equal a0_foo, unbound.super_method assert_equal a0_foo, unbound.super_method
@ -1289,7 +1289,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal 1, unbound.bind_call(obj) assert_equal 1, unbound.bind_call(obj)
assert_include b.instance_methods(false), :foo assert_include b.instance_methods(false), :foo
assert_equal "#<UnboundMethod: B(A0)#foo(arg1=..., arg2=...) #{__FILE__}:#{line0}>", b.instance_method(:foo).inspect assert_equal "#<UnboundMethod: A0#foo(arg1=..., arg2=...) #{__FILE__}:#{line0}>", b.instance_method(:foo).inspect
end end
def test_zsuper_method_redefined_bind_call def test_zsuper_method_redefined_bind_call