From 2a4c87dc31b3774b8f8f2ab7b74f6d864f41a804 Mon Sep 17 00:00:00 2001 From: nobu Date: Mon, 28 Jan 2019 11:45:21 +0000 Subject: Add refinements support to method/instance_method. [Fix GH-2034] From: manga_osyo git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@66935 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- method.h | 1 + proc.c | 8 ++-- spec/ruby/core/module/refine_spec.rb | 74 ++++++++++++++++++++++++++++++++---- test/ruby/test_refinement.rb | 41 +++++++++++++++++++- vm_method.c | 6 +++ 5 files changed, 116 insertions(+), 14 deletions(-) diff --git a/method.h b/method.h index 771e77e889..fdf234c8b4 100644 --- a/method.h +++ b/method.h @@ -196,6 +196,7 @@ rb_method_entry_t *rb_method_entry_create(ID called_id, VALUE klass, rb_method_v const rb_method_entry_t *rb_method_entry_at(VALUE obj, ID id); const rb_method_entry_t *rb_method_entry(VALUE klass, ID id); +const rb_method_entry_t *rb_method_entry_with_refinements(VALUE klass, ID id, VALUE *defined_class); const rb_method_entry_t *rb_method_entry_without_refinements(VALUE klass, ID id, VALUE *defined_class); const rb_method_entry_t *rb_resolve_refined_method(VALUE refinements, const rb_method_entry_t *me); RUBY_SYMBOL_EXPORT_BEGIN diff --git a/proc.c b/proc.c index 6241bf32ec..76728f280f 100644 --- a/proc.c +++ b/proc.c @@ -1413,7 +1413,7 @@ mnew_internal(const rb_method_entry_t *me, VALUE klass, VALUE iclass, if (me->defined_class) { VALUE klass = RCLASS_SUPER(RCLASS_ORIGIN(me->defined_class)); id = me->def->original_id; - me = (rb_method_entry_t *)rb_callable_method_entry_without_refinements(klass, id, &iclass); + me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(klass, id, &iclass); } else { VALUE klass = RCLASS_SUPER(me->owner); @@ -1448,10 +1448,10 @@ mnew(VALUE klass, VALUE obj, ID id, VALUE mclass, int scope) VALUE iclass = Qnil; if (obj == Qundef) { /* UnboundMethod */ - me = rb_method_entry_without_refinements(klass, id, &iclass); + me = rb_method_entry_with_refinements(klass, id, &iclass); } else { - me = (rb_method_entry_t *)rb_callable_method_entry_without_refinements(klass, id, &iclass); + me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(klass, id, &iclass); } return mnew_from_me(me, klass, iclass, obj, id, mclass, scope); } @@ -2766,7 +2766,7 @@ method_super_method(VALUE method) super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass)); mid = data->me->called_id; if (!super_class) return Qnil; - me = (rb_method_entry_t *)rb_callable_method_entry_without_refinements(super_class, mid, &iclass); + me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, mid, &iclass); if (!me) return Qnil; return mnew_internal(me, me->owner, iclass, data->recv, mid, rb_obj_class(method), FALSE, FALSE); } diff --git a/spec/ruby/core/module/refine_spec.rb b/spec/ruby/core/module/refine_spec.rb index 662d49205d..42b2b64331 100644 --- a/spec/ruby/core/module/refine_spec.rb +++ b/spec/ruby/core/module/refine_spec.rb @@ -526,20 +526,78 @@ describe "Module#refine" do result.should == "hello from refinement" end - it "is not honored by Kernel#method" do - klass = Class.new - refinement = Module.new do - refine klass do - def foo; end + ruby_version_is "" ... "2.7" do + it "is not honored by Kernel#method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end + end end + + -> { + Module.new do + using refinement + klass.new.method(:foo) + end + }.should raise_error(NameError, /undefined method `foo'/) end + end - -> { + ruby_version_is "2.7" do + it "is honored by Kernel#method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end + end + end + + result = nil Module.new do using refinement - klass.new.method(:foo) + result = klass.new.method(:foo).class end - }.should raise_error(NameError, /undefined method `foo'/) + + result.should == Method + end + end + + ruby_version_is "" ... "2.7" do + it "is not honored by Kernel#instance_method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end + end + end + + -> { + Module.new do + using refinement + klass.instance_method(:foo) + end + }.should raise_error(NameError, /undefined method `foo'/) + end + end + + ruby_version_is "2.7" do + it "is honored by Kernel#method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end + end + end + + result = nil + Module.new do + using refinement + result = klass.instance_method(:foo).class + end + + result.should == UnboundMethod + end end ruby_version_is "" ... "2.6" do diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index 5c06266d6e..ba571670cf 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -118,6 +118,10 @@ class TestRefinement < Test::Unit::TestCase return foo.method(:z) end + def self.instance_method_z(foo) + return foo.class.instance_method(:z) + end + def self.invoke_call_x_on(foo) return foo.call_x end @@ -213,11 +217,44 @@ class TestRefinement < Test::Unit::TestCase assert_raise(NoMethodError) { FooExtClient.public_send_b_on(foo) } end - def test_method_should_not_use_refinements + module MethodIntegerPowEx + refine Integer do + def pow(*) + :refine_pow + end + end + end + def test_method_should_use_refinements foo = Foo.new assert_raise(NameError) { foo.method(:z) } - assert_raise(NameError) { FooExtClient.method_z(foo) } + assert_equal("FooExt#z", FooExtClient.method_z(foo).call) assert_raise(NameError) { foo.method(:z) } + assert_equal(8, eval(<<~EOS, Sandbox::BINDING)) + meth = 2.method(:pow) + using MethodIntegerPowEx + meth.call(3) + EOS + assert_equal(:refine_pow, eval_using(MethodIntegerPowEx, "2.pow(3)")) + end + + module InstanceMethodIntegerPowEx + refine Integer do + def abs + :refine_abs + end + end + end + def test_instance_method_should_use_refinements + foo = Foo.new + assert_raise(NameError) { Foo.instance_method(:z) } + assert_equal("FooExt#z", FooExtClient.instance_method_z(foo).bind(foo).call) + assert_raise(NameError) { Foo.instance_method(:z) } + assert_equal(4, eval(<<~EOS, Sandbox::BINDING)) + meth = Integer.instance_method(:abs) + using InstanceMethodIntegerPowEx + meth.bind(-4).call + EOS + assert_equal(:refine_abs, eval_using(InstanceMethodIntegerPowEx, "Integer.instance_method(:abs).bind(-4).call")) end def test_no_local_rebinding diff --git a/vm_method.c b/vm_method.c index 3333ea16b4..76fdb2069f 100644 --- a/vm_method.c +++ b/vm_method.c @@ -899,6 +899,12 @@ method_entry_resolve_refinement(VALUE klass, ID id, int with_refinement, VALUE * return me; } +const rb_method_entry_t * +rb_method_entry_with_refinements(VALUE klass, ID id, VALUE *defined_class_ptr) +{ + return method_entry_resolve_refinement(klass, id, TRUE, defined_class_ptr); +} + MJIT_FUNC_EXPORTED const rb_callable_method_entry_t * rb_callable_method_entry_with_refinements(VALUE klass, ID id, VALUE *defined_class_ptr) { -- cgit v1.2.3