[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]