mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
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 ```
This commit is contained in:
parent
500149709b
commit
83c6a1ef45
3 changed files with 110 additions and 32 deletions
28
NEWS
28
NEWS
|
@ -160,6 +160,34 @@ Time::
|
||||||
|
|
||||||
* Added Time#floor method. [Feature #15653]
|
* 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::
|
$LOAD_PATH::
|
||||||
|
|
||||||
New method::
|
New method::
|
||||||
|
|
105
proc.c
105
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:
|
* call-seq:
|
||||||
* umeth.bind(obj) -> method
|
* umeth.bind(obj) -> method
|
||||||
|
@ -2356,46 +2396,46 @@ rb_method_call_with_block(int argc, const VALUE *argv, VALUE method, VALUE passe
|
||||||
static VALUE
|
static VALUE
|
||||||
umethod_bind(VALUE method, VALUE recv)
|
umethod_bind(VALUE method, VALUE recv)
|
||||||
{
|
{
|
||||||
struct METHOD *data, *bound;
|
|
||||||
VALUE methclass, klass;
|
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);
|
struct METHOD *bound;
|
||||||
|
|
||||||
methclass = data->me->owner;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
klass = CLASS_OF(recv);
|
|
||||||
|
|
||||||
method = TypedData_Make_Struct(rb_cMethod, struct METHOD, &method_data_type, 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->recv, recv);
|
||||||
RB_OBJ_WRITE(method, &bound->klass, data->klass);
|
RB_OBJ_WRITE(method, &bound->klass, klass);
|
||||||
RB_OBJ_WRITE(method, &bound->me, rb_method_entry_clone(data->me));
|
RB_OBJ_WRITE(method, &bound->me, 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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++;
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
||||||
|
VALUE passed_procval = rb_block_given_p() ? rb_block_proc() : Qnil;
|
||||||
|
|
||||||
|
rb_execution_context_t *ec = GET_EC();
|
||||||
|
return call_method_data(ec, &bound, argc, argv, passed_procval);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the number of required parameters and stores the maximum
|
* Returns the number of required parameters and stores the maximum
|
||||||
* number of parameters in max, or UNLIMITED_ARGUMENTS
|
* number of parameters in max, or UNLIMITED_ARGUMENTS
|
||||||
|
@ -3683,6 +3723,7 @@ Init_Proc(void)
|
||||||
rb_define_method(rb_cUnboundMethod, "original_name", method_original_name, 0);
|
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, "owner", method_owner, 0);
|
||||||
rb_define_method(rb_cUnboundMethod, "bind", umethod_bind, 1);
|
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, "source_location", rb_method_location, 0);
|
||||||
rb_define_method(rb_cUnboundMethod, "parameters", rb_method_parameters, 0);
|
rb_define_method(rb_cUnboundMethod, "parameters", rb_method_parameters, 0);
|
||||||
rb_define_method(rb_cUnboundMethod, "super_method", method_super_method, 0);
|
rb_define_method(rb_cUnboundMethod, "super_method", method_super_method, 0);
|
||||||
|
|
|
@ -1140,4 +1140,13 @@ class TestMethod < Test::Unit::TestCase
|
||||||
assert_equal(m, o.:foo)
|
assert_equal(m, o.:foo)
|
||||||
assert_nil(o.method(:foo))
|
assert_nil(o.method(:foo))
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue