ruby-changes:57392
From: Yusuke <ko1@a...>
Date: Fri, 30 Aug 2019 11:13:20 +0900 (JST)
Subject: [ruby-changes:57392] Yusuke Endoh: 83c6a1ef45 (master): proc.c: Add UnboundMethod#bind_call
https://git.ruby-lang.org/ruby.git/commit/?id=83c6a1ef45 From 83c6a1ef454c51ad1c0ca58e8a95fd67a033f710 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh <mame@r...> Date: Fri, 30 Aug 2019 11:01:25 +0900 Subject: proc.c: Add UnboundMethod#bind_call `umethod.bind_call(obj, ...)` is semantically equivalent to `umethod.bind(obj).call(...)`. This idiom is used in some libraries to call a method that is overridden. The added method does the same without allocation of intermediate Method object. [Feature #15955] ``` class Foo def add_1(x) x + 1 end end class Bar < Foo def add_1(x) # override x + 2 end end obj = Bar.new p obj.add_1(1) #=> 3 p Foo.instance_method(:add_1).bind(obj).call(1) #=> 2 p Foo.instance_method(:add_1).bind_call(obj, 1) #=> 2 ``` diff --git a/NEWS b/NEWS index 61f3fc8..34a9476 100644 --- a/NEWS +++ b/NEWS @@ -160,6 +160,34 @@ Time:: https://github.com/ruby/ruby/blob/trunk/NEWS#L160 * Added Time#floor method. [Feature #15653] +UnboundMethod:: + + New methods:: + + * Added UnboundMethod#bind_call method. [Feature #15955] + +`umethod.bind_call(obj, ...)` is semantically equivalent to +`umethod.bind(obj).call(...)`. This idiom is used in some libraries to +call a method that is overridden. The added method does the same +without allocation of intermediate Method object. + + class Foo + def add_1(x) + x + 1 + end + end + class Bar < Foo + def add_1 # override + x + 2 + end + end + + obj = Bar.new + p obj.add_1(1) #=> 3 + p Foo.instance_method(:add_1).bind(obj).call(1) #=> 2 + p Foo.instance_method(:add_1).bind_call(obj, 1) #=> 2 + + $LOAD_PATH:: New method:: diff --git a/proc.c b/proc.c index a99f3c9..0d8c4cd 100644 --- a/proc.c +++ b/proc.c @@ -2318,6 +2318,46 @@ rb_method_call_with_block(int argc, const VALUE *argv, VALUE method, VALUE passe https://github.com/ruby/ruby/blob/trunk/proc.c#L2318 * */ +static void +convert_umethod_to_method_components(VALUE method, VALUE recv, VALUE *methclass_out, VALUE *klass_out, const rb_method_entry_t **me_out) +{ + struct METHOD *data; + + TypedData_Get_Struct(method, struct METHOD, &method_data_type, data); + + VALUE methclass = data->me->owner; + VALUE klass = CLASS_OF(recv); + + if (!RB_TYPE_P(methclass, T_MODULE) && + methclass != CLASS_OF(recv) && !rb_obj_is_kind_of(recv, methclass)) { + if (FL_TEST(methclass, FL_SINGLETON)) { + rb_raise(rb_eTypeError, + "singleton method called for a different object"); + } + else { + rb_raise(rb_eTypeError, "bind argument must be an instance of % "PRIsVALUE, + methclass); + } + } + + const rb_method_entry_t *me = rb_method_entry_clone(data->me); + + if (RB_TYPE_P(me->owner, T_MODULE)) { + VALUE ic = rb_class_search_ancestor(klass, me->owner); + if (ic) { + klass = ic; + } + else { + klass = rb_include_class_new(methclass, klass); + } + me = (const rb_method_entry_t *) rb_method_entry_complement_defined_class(me, me->called_id, klass); + } + + *methclass_out = methclass; + *klass_out = klass; + *me_out = me; +} + /* * call-seq: * umeth.bind(obj) -> method @@ -2356,44 +2396,44 @@ rb_method_call_with_block(int argc, const VALUE *argv, VALUE method, VALUE passe https://github.com/ruby/ruby/blob/trunk/proc.c#L2396 static VALUE umethod_bind(VALUE method, VALUE recv) { - struct METHOD *data, *bound; VALUE methclass, klass; + const rb_method_entry_t *me; + convert_umethod_to_method_components(method, recv, &methclass, &klass, &me); - TypedData_Get_Struct(method, struct METHOD, &method_data_type, data); - - methclass = data->me->owner; + struct METHOD *bound; + method = TypedData_Make_Struct(rb_cMethod, struct METHOD, &method_data_type, bound); + RB_OBJ_WRITE(method, &bound->recv, recv); + RB_OBJ_WRITE(method, &bound->klass, klass); + RB_OBJ_WRITE(method, &bound->me, me); - if (!RB_TYPE_P(methclass, T_MODULE) && - methclass != CLASS_OF(recv) && !rb_obj_is_kind_of(recv, methclass)) { - if (FL_TEST(methclass, FL_SINGLETON)) { - rb_raise(rb_eTypeError, - "singleton method called for a different object"); - } - else { - rb_raise(rb_eTypeError, "bind argument must be an instance of % "PRIsVALUE, - methclass); - } - } + return method; +} - klass = CLASS_OF(recv); +/* + * call-seq: + * umeth.bind_call(obj, args, ...) -> obj + * + * Bind <i>umeth</i> to <i>obj</i> and then invokes the method with the + * specified arguments. + * This is semantically equivalent to <code>umeth.bind(obj).call(args, ...)</code>. + */ +static VALUE +umethod_bind_call(int argc, VALUE *argv, VALUE method) +{ + rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); + VALUE recv = argv[0]; + argc--; + argv++; - method = TypedData_Make_Struct(rb_cMethod, struct METHOD, &method_data_type, bound); - RB_OBJ_WRITE(method, &bound->recv, recv); - RB_OBJ_WRITE(method, &bound->klass, data->klass); - RB_OBJ_WRITE(method, &bound->me, rb_method_entry_clone(data->me)); + VALUE methclass, klass; + const rb_method_entry_t *me; + convert_umethod_to_method_components(method, recv, &methclass, &klass, &me); + struct METHOD bound = { recv, klass, 0, me }; - if (RB_TYPE_P(bound->me->owner, T_MODULE)) { - VALUE ic = rb_class_search_ancestor(klass, bound->me->owner); - if (ic) { - klass = ic; - } - else { - klass = rb_include_class_new(methclass, klass); - } - RB_OBJ_WRITE(method, &bound->me, rb_method_entry_complement_defined_class(bound->me, bound->me->called_id, klass)); - } + VALUE passed_procval = rb_block_given_p() ? rb_block_proc() : Qnil; - return method; + rb_execution_context_t *ec = GET_EC(); + return call_method_data(ec, &bound, argc, argv, passed_procval); } /* @@ -3683,6 +3723,7 @@ Init_Proc(void) https://github.com/ruby/ruby/blob/trunk/proc.c#L3723 rb_define_method(rb_cUnboundMethod, "original_name", method_original_name, 0); rb_define_method(rb_cUnboundMethod, "owner", method_owner, 0); rb_define_method(rb_cUnboundMethod, "bind", umethod_bind, 1); + rb_define_method(rb_cUnboundMethod, "bind_call", umethod_bind_call, -1); rb_define_method(rb_cUnboundMethod, "source_location", rb_method_location, 0); rb_define_method(rb_cUnboundMethod, "parameters", rb_method_parameters, 0); rb_define_method(rb_cUnboundMethod, "super_method", method_super_method, 0); diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index acaf43e..1d59ddb 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1140,4 +1140,13 @@ class TestMethod < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_method.rb#L1140 assert_equal(m, o.:foo) assert_nil(o.method(:foo)) end + + def test_umethod_bind_call + foo = Base.instance_method(:foo) + assert_equal(:base, foo.bind_call(Base.new)) + assert_equal(:base, foo.bind_call(Derived.new)) + + plus = Integer.instance_method(:+) + assert_equal(3, plus.bind_call(1, 2)) + end end -- cgit v0.10.2 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/