Skip to content
Permalink
Browse files

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
```
  • Loading branch information...
mame committed Aug 30, 2019
1 parent 5001497 commit 83c6a1ef454c51ad1c0ca58e8a95fd67a033f710
Showing with 109 additions and 31 deletions.
  1. +28 −0 NEWS
  2. +72 −31 proc.c
  3. +9 −0 test/ruby/test_method.rb
28 NEWS
@@ -160,6 +160,34 @@ Time::

* 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::
103 proc.c
@@ -2318,6 +2318,46 @@ rb_method_call_with_block(int argc, const VALUE *argv, VALUE method, VALUE passe
*
*/

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
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)
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);
@@ -1140,4 +1140,13 @@ def o.method(m); nil; end
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

0 comments on commit 83c6a1e

Please sign in to comment.
You can’t perform that action at this time.