diff options
Diffstat (limited to 'mrbgems/mruby-method')
| -rw-r--r-- | mrbgems/mruby-method/README.md | 60 | ||||
| -rw-r--r-- | mrbgems/mruby-method/mrbgem.rake | 7 | ||||
| -rw-r--r-- | mrbgems/mruby-method/mrblib/kernel.rb | 10 | ||||
| -rw-r--r-- | mrbgems/mruby-method/mrblib/method.rb | 16 | ||||
| -rw-r--r-- | mrbgems/mruby-method/src/method.c | 453 | ||||
| -rw-r--r-- | mrbgems/mruby-method/test/method.rb | 451 |
6 files changed, 997 insertions, 0 deletions
diff --git a/mrbgems/mruby-method/README.md b/mrbgems/mruby-method/README.md new file mode 100644 index 000000000..5cb0b342a --- /dev/null +++ b/mrbgems/mruby-method/README.md @@ -0,0 +1,60 @@ +mruby-method +=== + +A implementation of class **Method** and **UnboundMethod** for mruby + +```ruby +p Enumerable.instance_method(:find_all).source_location +#=> ["mruby/mruby/mrblib/enum.rb", 148] +``` + +# Note + +You need to enable debug option in your build configuration to use +`source_location` method in this gem, for example: + +```ruby +MRuby::Build.new do |conf| + conf.enable_debug +end +``` + +# Supported Methods + +## Kernel + +- `Kernel#method` +- `Kernel#singleton_method` + +## Module + +- `Module#instance_method` + +## Method class + +- `Method#name` +- `Method#call` +- `Method#super_method` +- `Method#arity` +- `Method#unbind` +- `Method#[]` +- `Method#owner` +- `Method#receiver` +- `Method#parameters` +- `Method#source_location` +- `Method#to_proc` + +## UnboundMethod class + +- `UnboundMethod#name` +- `UnboundMethod#bind` +- `UnboundMethod#super_method` +- `UnboundMethod#arity` +- `UnboundMethod#owner` +- `UnboundMethod#parameters` +- `UnboundMethod#source_location` + +# See also + +- https://ruby-doc.org/core-2.3.3/Method.html +- https://ruby-doc.org/core-2.3.3/UnboundMethod.html diff --git a/mrbgems/mruby-method/mrbgem.rake b/mrbgems/mruby-method/mrbgem.rake new file mode 100644 index 000000000..dca0c3af8 --- /dev/null +++ b/mrbgems/mruby-method/mrbgem.rake @@ -0,0 +1,7 @@ +MRuby::Gem::Specification.new('mruby-method') do |spec| + spec.license = 'MIT' + spec.author = 'mruby developers' + spec.summary = 'Method and UnboundMethod class' + + spec.add_dependency('mruby-proc-ext', :core => 'mruby-proc-ext') +end diff --git a/mrbgems/mruby-method/mrblib/kernel.rb b/mrbgems/mruby-method/mrblib/kernel.rb new file mode 100644 index 000000000..eb17df5a6 --- /dev/null +++ b/mrbgems/mruby-method/mrblib/kernel.rb @@ -0,0 +1,10 @@ +module Kernel + def singleton_method(name) + m = method(name) + sc = (class <<self; self; end) + if m.owner != sc + raise NameError, "undefined method '#{name}' for class '#{sc}'" + end + m + end +end diff --git a/mrbgems/mruby-method/mrblib/method.rb b/mrbgems/mruby-method/mrblib/method.rb new file mode 100644 index 000000000..56af7cf61 --- /dev/null +++ b/mrbgems/mruby-method/mrblib/method.rb @@ -0,0 +1,16 @@ +class Method + def to_proc + m = self + lambda { |*args, &b| + m.call(*args, &b) + } + end + + def <<(other) + ->(*args, &block) { call(other.call(*args, &block)) } + end + + def >>(other) + ->(*args, &block) { other.call(call(*args, &block)) } + end +end diff --git a/mrbgems/mruby-method/src/method.c b/mrbgems/mruby-method/src/method.c new file mode 100644 index 000000000..5d3a6c46a --- /dev/null +++ b/mrbgems/mruby-method/src/method.c @@ -0,0 +1,453 @@ +#include "mruby.h" +#include "mruby/array.h" +#include "mruby/class.h" +#include "mruby/variable.h" +#include "mruby/proc.h" +#include "mruby/string.h" +#include "mruby/presym.h" + +static struct RObject * +method_object_alloc(mrb_state *mrb, struct RClass *mclass) +{ + return (struct RObject*)mrb_obj_alloc(mrb, MRB_TT_OBJECT, mclass); +} + +static void +bind_check(mrb_state *mrb, mrb_value recv, mrb_value owner) +{ + if (!mrb_module_p(owner) && + mrb_class_ptr(owner) != mrb_obj_class(mrb, recv) && + !mrb_obj_is_kind_of(mrb, recv, mrb_class_ptr(owner))) { + if (mrb_sclass_p(owner)) { + mrb_raise(mrb, E_TYPE_ERROR, "singleton method called for a different object"); + } else { + mrb_raisef(mrb, E_TYPE_ERROR, "bind argument must be an instance of %v", owner); + } + } +} + +static mrb_value +unbound_method_bind(mrb_state *mrb, mrb_value self) +{ + struct RObject *me; + mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner)); + mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name)); + mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc)); + mrb_value klass = mrb_iv_get(mrb, self, MRB_SYM(_klass)); + mrb_value recv = mrb_get_arg1(mrb); + + bind_check(mrb, recv, owner); + me = method_object_alloc(mrb, mrb_class_get_id(mrb, MRB_SYM(Method))); + mrb_obj_iv_set(mrb, me, MRB_SYM(_owner), owner); + mrb_obj_iv_set(mrb, me, MRB_SYM(_recv), recv); + mrb_obj_iv_set(mrb, me, MRB_SYM(_name), name); + mrb_obj_iv_set(mrb, me, MRB_SYM(_proc), proc); + mrb_obj_iv_set(mrb, me, MRB_SYM(_klass), klass); + + return mrb_obj_value(me); +} + +#define IV_GET(value, name) mrb_iv_get(mrb, value, name) +static mrb_value +method_eql(mrb_state *mrb, mrb_value self) +{ + mrb_value other = mrb_get_arg1(mrb); + mrb_value receiver, orig_proc, other_proc; + struct RClass *owner, *klass; + struct RProc *orig_rproc, *other_rproc; + + if (!mrb_obj_is_instance_of(mrb, other, mrb_class(mrb, self))) + return mrb_false_value(); + + if (mrb_class(mrb, self) != mrb_class(mrb, other)) + return mrb_false_value(); + + klass = mrb_class_ptr(IV_GET(self, MRB_SYM(_klass))); + if (klass != mrb_class_ptr(IV_GET(other, MRB_SYM(_klass)))) + return mrb_false_value(); + + owner = mrb_class_ptr(IV_GET(self, MRB_SYM(_owner))); + if (owner != mrb_class_ptr(IV_GET(other, MRB_SYM(_owner)))) + return mrb_false_value(); + + receiver = IV_GET(self, MRB_SYM(_recv)); + if (!mrb_obj_equal(mrb, receiver, IV_GET(other, MRB_SYM(_recv)))) + return mrb_false_value(); + + orig_proc = IV_GET(self, MRB_SYM(_proc)); + other_proc = IV_GET(other, MRB_SYM(_proc)); + if (mrb_nil_p(orig_proc) && mrb_nil_p(other_proc)) { + if (mrb_symbol(IV_GET(self, MRB_SYM(_name))) == mrb_symbol(IV_GET(other, MRB_SYM(_name)))) + return mrb_true_value(); + else + return mrb_false_value(); + } + + if (mrb_nil_p(orig_proc)) + return mrb_false_value(); + if (mrb_nil_p(other_proc)) + return mrb_false_value(); + + orig_rproc = mrb_proc_ptr(orig_proc); + other_rproc = mrb_proc_ptr(other_proc); + if (MRB_PROC_CFUNC_P(orig_rproc)) { + if (!MRB_PROC_CFUNC_P(other_rproc)) + return mrb_false_value(); + if (orig_rproc->body.func != other_rproc->body.func) + return mrb_false_value(); + } + else { + if (MRB_PROC_CFUNC_P(other_rproc)) + return mrb_false_value(); + if (orig_rproc->body.irep != other_rproc->body.irep) + return mrb_false_value(); + } + + return mrb_true_value(); +} + +#undef IV_GET + +static mrb_value +mcall(mrb_state *mrb, mrb_value recv, mrb_value proc, mrb_value name, struct RClass *owner, + mrb_int argc, const mrb_value *argv, mrb_value block) +{ + mrb_value ret; + mrb_sym orig_mid = mrb->c->ci->mid; + + mrb->c->ci->mid = mrb_symbol(name); + if (mrb_nil_p(proc)) { + mrb_value missing_argv = mrb_ary_new_from_values(mrb, argc, argv); + mrb_ary_unshift(mrb, missing_argv, name); + ret = mrb_funcall_argv(mrb, recv, MRB_SYM(method_missing), argc + 1, RARRAY_PTR(missing_argv)); + } + else if (!mrb_nil_p(block)) { + /* + workaround since `mrb_yield_with_class` does not support passing block as parameter + need new API that initializes `mrb->c->stack[argc+1]` with block passed by argument + */ + ret = mrb_funcall_with_block(mrb, recv, mrb_symbol(name), argc, argv, block); + } + else { + ret = mrb_yield_with_class(mrb, proc, argc, argv, recv, owner); + } + mrb->c->ci->mid = orig_mid; + return ret; +} + +static mrb_value +method_call(mrb_state *mrb, mrb_value self) +{ + mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc)); + mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name)); + mrb_value recv = mrb_iv_get(mrb, self, MRB_SYM(_recv)); + struct RClass *owner = mrb_class_ptr(mrb_iv_get(mrb, self, MRB_SYM(_owner))); + mrb_int argc; + const mrb_value *argv; + mrb_value block; + + mrb_get_args(mrb, "*&", &argv, &argc, &block); + return mcall(mrb, recv, proc, name, owner, argc, argv, block); +} + +static mrb_value +method_bcall(mrb_state *mrb, mrb_value self) +{ + mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc)); + mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name)); + mrb_value recv = mrb_iv_get(mrb, self, MRB_SYM(_recv)); + mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner)); + mrb_int argc; + const mrb_value *argv; + mrb_value block; + + mrb_get_args(mrb, "o*&", &recv, &argv, &argc, &block); + bind_check(mrb, recv, owner); + return mcall(mrb, recv, proc, name, mrb_class_ptr(owner), argc, argv, block); +} + +static mrb_value +method_unbind(mrb_state *mrb, mrb_value self) +{ + struct RObject *ume; + mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner)); + mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name)); + mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc)); + mrb_value klass = mrb_iv_get(mrb, self, MRB_SYM(_klass)); + + ume = method_object_alloc(mrb, mrb_class_get(mrb, "UnboundMethod")); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_owner), owner); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_recv), mrb_nil_value()); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_name), name); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_proc), proc); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_klass), klass); + + return mrb_obj_value(ume); +} + +static struct RProc * +method_search_vm(mrb_state *mrb, struct RClass **cp, mrb_sym mid) +{ + mrb_method_t m = mrb_method_search_vm(mrb, cp, mid); + if (MRB_METHOD_UNDEF_P(m)) + return NULL; + if (MRB_METHOD_PROC_P(m)) + return MRB_METHOD_PROC(m); + return mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m)); +} + +static mrb_value +method_super_method(mrb_state *mrb, mrb_value self) +{ + mrb_value recv = mrb_iv_get(mrb, self, MRB_SYM(_recv)); + mrb_value klass = mrb_iv_get(mrb, self, MRB_SYM(_klass)); + mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner)); + mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name)); + struct RClass *super, *rklass; + struct RProc *proc; + struct RObject *me; + + switch (mrb_type(klass)) { + case MRB_TT_SCLASS: + super = mrb_class_ptr(klass)->super->super; + break; + case MRB_TT_ICLASS: + super = mrb_class_ptr(klass)->super; + break; + default: + super = mrb_class_ptr(owner)->super; + break; + } + + proc = method_search_vm(mrb, &super, mrb_symbol(name)); + if (!proc) + return mrb_nil_value(); + + rklass = super; + while (super->tt == MRB_TT_ICLASS) + super = super->c; + + me = method_object_alloc(mrb, mrb_obj_class(mrb, self)); + mrb_obj_iv_set(mrb, me, MRB_SYM(_owner), mrb_obj_value(super)); + mrb_obj_iv_set(mrb, me, MRB_SYM(_recv), recv); + mrb_obj_iv_set(mrb, me, MRB_SYM(_name), name); + mrb_obj_iv_set(mrb, me, MRB_SYM(_proc), mrb_obj_value(proc)); + mrb_obj_iv_set(mrb, me, MRB_SYM(_klass), mrb_obj_value(rklass)); + + return mrb_obj_value(me); +} + +static mrb_value +method_arity(mrb_state *mrb, mrb_value self) +{ + mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc)); + mrb_int arity = mrb_nil_p(proc) ? -1 : mrb_proc_arity(mrb_proc_ptr(proc)); + return mrb_fixnum_value(arity); +} + +static mrb_value +method_source_location(mrb_state *mrb, mrb_value self) +{ + mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc)); + struct RProc *rproc; + struct RClass *orig; + mrb_value ret; + + if (mrb_nil_p(proc)) + return mrb_nil_value(); + + rproc = mrb_proc_ptr(proc); + orig = rproc->c; + rproc->c = mrb->proc_class; + ret = mrb_funcall_id(mrb, proc, MRB_SYM(source_location), 0); + rproc->c = orig; + return ret; +} + +static mrb_value +method_parameters(mrb_state *mrb, mrb_value self) +{ + mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc)); + struct RProc *rproc; + struct RClass *orig; + mrb_value ret; + + if (mrb_nil_p(proc)) { + mrb_value rest = mrb_symbol_value(MRB_SYM(rest)); + mrb_value arest = mrb_ary_new_from_values(mrb, 1, &rest); + return mrb_ary_new_from_values(mrb, 1, &arest); + } + + rproc = mrb_proc_ptr(proc); + orig = rproc->c; + rproc->c = mrb->proc_class; + ret = mrb_funcall_id(mrb, proc, MRB_SYM(parameters), 0); + rproc->c = orig; + return ret; +} + +static mrb_value +method_to_s(mrb_state *mrb, mrb_value self) +{ + mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner)); + mrb_value klass = mrb_iv_get(mrb, self, MRB_SYM(_klass)); + mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name)); + mrb_value str = mrb_str_new_lit(mrb, "#<"); + struct RClass *rklass; + + mrb_str_cat_cstr(mrb, str, mrb_obj_classname(mrb, self)); + mrb_str_cat_lit(mrb, str, ": "); + rklass = mrb_class_ptr(klass); + if (mrb_class_ptr(owner) == rklass) { + mrb_str_concat(mrb, str, owner); + mrb_str_cat_lit(mrb, str, "#"); + mrb_str_concat(mrb, str, name); + } + else { + mrb_str_cat_cstr(mrb, str, mrb_class_name(mrb, rklass)); + mrb_str_cat_lit(mrb, str, "("); + mrb_str_concat(mrb, str, owner); + mrb_str_cat_lit(mrb, str, ")#"); + mrb_str_concat(mrb, str, name); + } + mrb_str_cat_lit(mrb, str, ">"); + return str; +} + +static void +mrb_search_method_owner(mrb_state *mrb, struct RClass *c, mrb_value obj, mrb_sym name, struct RClass **owner, struct RProc **proc, mrb_bool unbound) +{ + mrb_value ret; + + *owner = c; + *proc = method_search_vm(mrb, owner, name); + if (!*proc) { + if (unbound) { + goto name_error; + } + if (!mrb_respond_to(mrb, obj, MRB_SYM_Q(respond_to_missing))) { + goto name_error; + } + ret = mrb_funcall_id(mrb, obj, MRB_SYM_Q(respond_to_missing), 2, mrb_symbol_value(name), mrb_true_value()); + if (!mrb_test(ret)) { + goto name_error; + } + *owner = c; + } + + while ((*owner)->tt == MRB_TT_ICLASS) + *owner = (*owner)->c; + + return; + +name_error: + mrb_raisef(mrb, E_NAME_ERROR, "undefined method '%n' for class '%C'", name, c); +} + +static mrb_value +mrb_kernel_method(mrb_state *mrb, mrb_value self) +{ + struct RClass *owner; + struct RProc *proc; + struct RObject *me; + mrb_sym name; + + mrb_get_args(mrb, "n", &name); + + mrb_search_method_owner(mrb, mrb_class(mrb, self), self, name, &owner, &proc, FALSE); + + me = method_object_alloc(mrb, mrb_class_get(mrb, "Method")); + mrb_obj_iv_set(mrb, me, MRB_SYM(_owner), mrb_obj_value(owner)); + mrb_obj_iv_set(mrb, me, MRB_SYM(_recv), self); + mrb_obj_iv_set(mrb, me, MRB_SYM(_name), mrb_symbol_value(name)); + mrb_obj_iv_set(mrb, me, MRB_SYM(_proc), proc ? mrb_obj_value(proc) : mrb_nil_value()); + mrb_obj_iv_set(mrb, me, MRB_SYM(_klass), mrb_obj_value(mrb_class(mrb, self))); + + return mrb_obj_value(me); +} + +static mrb_value +mrb_module_instance_method(mrb_state *mrb, mrb_value self) +{ + struct RClass *owner; + struct RProc *proc; + struct RObject *ume; + mrb_sym name; + + mrb_get_args(mrb, "n", &name); + + mrb_search_method_owner(mrb, mrb_class_ptr(self), self, name, &owner, &proc, TRUE); + + ume = method_object_alloc(mrb, mrb_class_get(mrb, "UnboundMethod")); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_owner), mrb_obj_value(owner)); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_recv), mrb_nil_value()); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_name), mrb_symbol_value(name)); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_proc), proc ? mrb_obj_value(proc) : mrb_nil_value()); + mrb_obj_iv_set(mrb, ume, MRB_SYM(_klass), self); + + return mrb_obj_value(ume); +} + +static mrb_value +method_owner(mrb_state *mrb, mrb_value self) +{ + return mrb_iv_get(mrb, self, MRB_SYM(_owner)); +} + +static mrb_value +method_receiver(mrb_state *mrb, mrb_value self) +{ + return mrb_iv_get(mrb, self, MRB_SYM(_recv)); +} + +static mrb_value +method_name(mrb_state *mrb, mrb_value self) +{ + return mrb_iv_get(mrb, self, MRB_SYM(_name)); +} + +void +mrb_mruby_method_gem_init(mrb_state* mrb) +{ + struct RClass *unbound_method = mrb_define_class(mrb, "UnboundMethod", mrb->object_class); + struct RClass *method = mrb_define_class(mrb, "Method", mrb->object_class); + + mrb_undef_class_method(mrb, unbound_method, "new"); + mrb_define_method(mrb, unbound_method, "bind", unbound_method_bind, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, unbound_method, "super_method", method_super_method, MRB_ARGS_NONE()); + mrb_define_method(mrb, unbound_method, "==", method_eql, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, unbound_method, "eql?", method_eql, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, unbound_method, "to_s", method_to_s, MRB_ARGS_NONE()); + mrb_define_method(mrb, unbound_method, "inspect", method_to_s, MRB_ARGS_NONE()); + mrb_define_method(mrb, unbound_method, "arity", method_arity, MRB_ARGS_NONE()); + mrb_define_method(mrb, unbound_method, "source_location", method_source_location, MRB_ARGS_NONE()); + mrb_define_method(mrb, unbound_method, "parameters", method_parameters, MRB_ARGS_NONE()); + mrb_define_method(mrb, unbound_method, "bind_call", method_bcall, MRB_ARGS_REQ(1)|MRB_ARGS_ANY()); + mrb_define_method(mrb, unbound_method, "owner", method_owner, MRB_ARGS_NONE()); + mrb_define_method(mrb, unbound_method, "name", method_name, MRB_ARGS_NONE()); + + mrb_undef_class_method(mrb, method, "new"); + mrb_define_method(mrb, method, "==", method_eql, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, method, "eql?", method_eql, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, method, "to_s", method_to_s, MRB_ARGS_NONE()); + mrb_define_method(mrb, method, "inspect", method_to_s, MRB_ARGS_NONE()); + mrb_define_method(mrb, method, "call", method_call, MRB_ARGS_ANY()); + mrb_define_method(mrb, method, "[]", method_call, MRB_ARGS_ANY()); + mrb_define_method(mrb, method, "unbind", method_unbind, MRB_ARGS_NONE()); + mrb_define_method(mrb, method, "super_method", method_super_method, MRB_ARGS_NONE()); + mrb_define_method(mrb, method, "arity", method_arity, MRB_ARGS_NONE()); + mrb_define_method(mrb, method, "source_location", method_source_location, MRB_ARGS_NONE()); + mrb_define_method(mrb, method, "parameters", method_parameters, MRB_ARGS_NONE()); + mrb_define_method(mrb, method, "owner", method_owner, MRB_ARGS_NONE()); + mrb_define_method(mrb, method, "receiver", method_receiver, MRB_ARGS_NONE()); + mrb_define_method(mrb, method, "name", method_name, MRB_ARGS_NONE()); + + mrb_define_method(mrb, mrb->kernel_module, "method", mrb_kernel_method, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, mrb->module_class, "instance_method", mrb_module_instance_method, MRB_ARGS_REQ(1)); +} + +void +mrb_mruby_method_gem_final(mrb_state* mrb) +{ +} diff --git a/mrbgems/mruby-method/test/method.rb b/mrbgems/mruby-method/test/method.rb new file mode 100644 index 000000000..123ae34be --- /dev/null +++ b/mrbgems/mruby-method/test/method.rb @@ -0,0 +1,451 @@ +class Base + def foo() :base end +end + +class Derived < Base + def foo() :derived end +end + +class Interpreter + attr_accessor :ret + + def do_a() @ret += "there, "; end + def do_d() @ret += "Hello "; end + def do_e() @ret += "!\n"; end + def do_v() @ret += "Dave"; end + Dispatcher = { + "a" => instance_method(:do_a), + "d" => instance_method(:do_d), + "e" => instance_method(:do_e), + "v" => instance_method(:do_v) + } + def interpret(string) + @ret = "" + string.split("").each {|b| Dispatcher[b].bind(self).call } + end +end + +assert 'demo' do + interpreter = Interpreter.new + interpreter.interpret('dave') + assert_equal "Hello there, Dave!\n", interpreter.ret +end + +assert 'Method#arity' do + Class.new { + attr_accessor :done + def initialize; @done = false; end + def m0() end + def m1(a) end + def m2(a, b) end + def mo1(a = nil, &b) end + def mo2(a, b = nil) end + def mo3(*a) end + def mo4(a, *b, &c) end + def mo5(a, *b, c) end + def mo6(a, *b, c, &d) end + def mo7(a, b = nil, *c, d, &e) end + def ma1((a), &b) nil && a end + + def run + assert_equal(0, method(:m0).arity) + assert_equal(1, method(:m1).arity) + assert_equal(2, method(:m2).arity) + assert_equal(-1, method(:mo1).arity) + assert_equal(-2, method(:mo2).arity) + assert_equal(-1, method(:mo3).arity) + assert_equal(-2, method(:mo4).arity) + assert_equal(-3, method(:mo5).arity) + assert_equal(-3, method(:mo6).arity) + assert_equal(-3, method(:mo7).arity) + assert_equal(1, method(:ma1).arity) + + assert_equal(-1, method(:__send__).arity) + assert_equal(-1, method(:nothing).arity) + end + + def respond_to_missing?(m, b) + m == :nothing + end + }.new.run +end + +assert 'Method and UnboundMethod should not be have a `new` method' do + assert_raise(NoMethodError){ Method.new } + assert_raise(NoMethodError){ UnboundMethod.new } +end + +assert 'instance' do + assert_kind_of Method, 1.method(:+) + assert_kind_of UnboundMethod, Integer.instance_method(:+) +end + +assert 'Method#call' do + assert_equal 3, 1.method(:+).call(2) + assert_equal "ab", "a".method(:+)["b"] + klass = Class.new { + def foo; 42; end + } + klass2 = Class.new(klass) { + def foo; super; end + } + assert_equal 42, klass2.new.method(:foo).call + + i = Class.new { + def bar + yield 3 + end + }.new + assert_raise(LocalJumpError) { i.method(:bar).call } + assert_equal 3, i.method(:bar).call { |i| i } +end + +assert 'Method#call for regression' do + obj = BasicObject.new + assert_equal String, Kernel.instance_method(:inspect).bind(obj).call().class, "https://github.com/ksss/mruby-method/issues/4" +end + +assert 'Method#call with undefined method' do + c = Class.new { + attr_accessor :m, :argv + def respond_to_missing?(m, b) + m == :foo + end + + def method_missing(m, *argv) + @m = m + @argv = argv + super + end + } + cc = c.new + assert_raise(NameError) { cc.method(:nothing) } + assert_kind_of Method, cc.method(:foo) + assert_raise(NoMethodError) { cc.method(:foo).call(:arg1, :arg2) } + assert_equal :foo, cc.m + assert_equal [:arg1, :arg2], cc.argv + + cc = c.new + m = cc.method(:foo) + c.class_eval do + def foo + :ng + end + end + assert_raise(NoMethodError) { m.call(:arg1, :arg2) } +end + +assert 'Method#source_location' do + skip if proc{}.source_location.nil? + + filename = __FILE__ + klass = Class.new + + lineno = __LINE__ + 1 + klass.define_method(:find_me_if_you_can) {} + assert_equal [filename, lineno], klass.new.method(:find_me_if_you_can).source_location + + lineno = __LINE__ + 1 + class <<klass; define_method(:s_find_me_if_you_can) {}; end + assert_equal [filename, lineno], klass.method(:s_find_me_if_you_can).source_location + + klass = Class.new { def respond_to_missing?(m, b); m == :nothing; end } + assert_nil klass.new.method(:nothing).source_location +end + +assert 'UnboundMethod#source_location' do + skip if proc{}.source_location.nil? + + filename = __FILE__ + klass = Class.new { + def respond_to_missing?(m, b) + m == :nothing + end + } + + lineno = __LINE__ + 1 + klass.define_method(:find_me_if_you_can) {} + assert_equal [filename, lineno], klass.instance_method(:find_me_if_you_can).source_location + assert_nil klass.new.method(:nothing).unbind.source_location +end + +assert 'Method#parameters' do + klass = Class.new { + def foo(a, b=nil, *c) end + def respond_to_missing?(m, b) + m == :missing + end + } + assert_equal [[:req, :a], [:opt, :b], [:rest, :c]], klass.new.method(:foo).parameters + assert_equal [[:rest]], klass.new.method(:missing).parameters +end + +assert 'UnboundMethod#parameters' do + klass = Class.new { + def foo(a, b=nil, *c) end + def respond_to_missing?(m, b) + m == :nothing + end + } + assert_equal [[:req, :a], [:opt, :b], [:rest, :c]], klass.instance_method(:foo).parameters + assert_equal [[:rest]], klass.new.method(:nothing).unbind.parameters +end + +assert 'Method#to_proc' do + m = 3.method(:+) + assert_kind_of Proc, m.to_proc + assert_equal 7, m.call(4) + + o = Object.new + def o.foo(a, b=nil, *c) + [a, b, c] + end + assert_equal [:bar, nil, []], o.method(:foo).to_proc.call(:bar) +# We can fix this issue but leave until the problem +# assert_equal o.method(:foo).arity, o.method(:foo).to_proc.arity + + def o.bar + yield 39 + end + assert_equal 42, o.bar(&3.method(:+)) +end + +assert 'to_s' do + o = Object.new + def o.foo; end + m = o.method(:foo) + assert_equal("#<UnboundMethod: #{ class << o; self; end.inspect }#foo>", m.unbind.inspect) + + c = Class.new + c.class_eval { def foo; end; } + m = c.new.method(:foo) + assert_equal("#<Method: #{ c.inspect }#foo>", m.inspect) + m = c.instance_method(:foo) + assert_equal("#<UnboundMethod: #{ c.inspect }#foo>", m.inspect) +end + +assert 'owner' do + c = Class.new do + def foo; end + def self.bar; end + end + m = Module.new do + def baz; end + end + c.include(m) + c2 = Class.new(c) + + assert_equal(c, c.instance_method(:foo).owner) + assert_equal(c, c2.instance_method(:foo).owner) + + assert_equal(c, c.new.method(:foo).owner) + assert_equal(c, c2.new.method(:foo).owner) + assert_equal((class <<c; self; end), c2.method(:bar).owner) +end + +assert 'owner missing' do + c = Class.new do + def respond_to_missing?(name, bool) + name == :foo + end + end + c2 = Class.new(c) + assert_equal(c, c.new.method(:foo).owner) + assert_equal(c2, c2.new.method(:foo).owner) +end + +assert 'receiver name owner' do + o = Object.new + def o.foo; end + m = o.method(:foo) + assert_equal(o, m.receiver) + assert_equal(:foo, m.name) + assert_equal(class << o; self; end, m.owner) + assert_equal(:foo, m.unbind.name) + assert_equal(class << o; self; end, m.unbind.owner) +end + +assert 'Method#unbind' do + assert_equal(:derived, Derived.new.foo) + um = Derived.new.method(:foo).unbind + assert_kind_of(UnboundMethod, um) + Derived.class_eval do + def foo() :changed end + end + assert_equal(:changed, Derived.new.foo) + assert_equal(:changed, Derived.new.foo{}) + assert_equal(:derived, um.bind(Derived.new).call) + assert_raise(TypeError) do + um.bind(Base.new) + end + + # TODO: + # Block passed method not handled correctly with workaround. + # See comment near `mrb_funcall_with_block` for detail. + # assert_equal(:derived, um.bind(Derived.new).call{}) +end + +assert 'Kernel#method' do + c1 = Class.new { + def foo; :foo; end + } + o = c1.new + assert_kind_of Method, o.method(:foo) + assert_kind_of Method, o.method('foo') + assert_raise(TypeError) { o.method(nil) } + assert_raise(NameError) { o.method('bar') } + assert_raise(NameError) { o.method(:bar) } +end + +assert "Module#instance_method" do + assert_kind_of UnboundMethod, Object.instance_method(:object_id) + assert_raise(NameError) { Object.instance_method(:nothing) } + c = Class.new { + def respond_to_missing?(m, b) + false + end + } + assert_raise(NameError) { c.instance_method(:nothing) } +end + +assert 'Kernel#singleton_method' do + c1 = Class.new { + def foo; :foo; end + } + o = c1.new + def o.bar; :bar; end + assert_kind_of Method, o.method(:foo) + assert_raise(NameError) { o.singleton_method(:foo) } + assert_kind_of Method, o.singleton_method(:bar) + assert_raise(TypeError) { o.singleton_method(nil) } + m = assert_nothing_raised(NameError) { break o.singleton_method(:bar) } + assert_equal(:bar, m.call) +end + +assert 'Method#super_method' do + o = Derived.new + m = o.method(:foo).super_method + assert_equal(Base, m.owner) + assert_true(o.equal? m.receiver) + assert_equal(:foo, m.name) + assert_nil(m.super_method) + + c = Class.new { + def foo; end + } + o = c.new + o.extend Module.new { + def foo; end + } + assert_equal c, o.method(:foo).super_method.owner + assert_equal :foo, o.method(:foo).super_method.name + assert_equal o, o.method(:foo).super_method.receiver +end + +assert 'Method#==' do + o = Object.new + class << o + def foo; end + end + assert_not_equal(o.method(:foo), nil) + m = o.method(:foo) + def m.foo; end + # TODO: assert_not_equal(o.method(:foo), m) + assert_equal(o.method(:foo), o.method(:foo)) + # TODO: assert_false(o.method(:foo).eql? m) + assert_true(o.method(:foo).eql? o.method(:foo)) + + assert_false(0.method(:+) == 1.method(:+)) + assert_false(0.method(:+) == 0.method(:-)) + a = 0.method(:+) + assert_true(a.method(:==) == a.method(:eql?)) +end + +assert "Method#initialize_copy" do + c = Class.new { + def foo + end + }.new + m1 = c.method(:foo) + m2 = m1.clone + assert_equal(m1, m2) +end + +assert "Method#<< and Method#>>" do + obj = Object.new + class << obj + def mul2(n); n * 2; end + def add3(n); n + 3; end + end + + f = obj.method(:mul2) + g = obj.method(:add3) + + m1 = f << g + assert_kind_of Proc, m1 + assert_equal 16, m1.call(5) + + m2 = f >> g + assert_kind_of Proc, m2 + assert_equal 13, m2.call(5) +end + +assert 'UnboundMethod#arity' do + c = Class.new { + def foo(a, b) + end + + def respond_to_missing?(m, b) + m == :nothing + end + } + assert_equal 2, c.instance_method(:foo).arity + assert_equal(-1, c.new.method(:nothing).unbind.arity) +end + +assert 'UnboundMethod#==' do + assert_false(Integer.instance_method(:+) == Integer.instance_method(:-)) + assert_true(Integer.instance_method(:+) == Integer.instance_method(:+)) + assert_false(Integer.instance_method(:+) == Float.instance_method(:+)) + assert_true(UnboundMethod.instance_method(:==) == UnboundMethod.instance_method(:eql?)) +end + +assert 'UnboundMethod#super_method' do + m = Derived.instance_method(:foo) + m = m.super_method + assert_equal(Base.instance_method(:foo), m) + assert_nil(m.super_method) + + m = Object.instance_method(:object_id) + assert_nil(m.super_method) +end + +assert 'UnboundMethod#bind' do + m = Module.new{ def meth() :meth end }.instance_method(:meth) + assert_raise(ArgumentError) { m.bind } + assert_kind_of Method, m.bind(1) + assert_kind_of Method, m.bind(:sym) + assert_kind_of Method, m.bind(Object.new) + assert_equal(:meth, m.bind(1).call) + assert_equal(:meth, m.bind(:sym).call) + assert_equal(:meth, m.bind(Object.new).call) + sc = nil + Class.new { + sc = class << self + def foo + end + self + end + } + assert_raise(TypeError) { sc.instance_method(:foo).bind([]) } + assert_raise(TypeError) { Array.instance_method(:each).bind(1) } + assert_kind_of Method, Object.instance_method(:object_id).bind(Object.new) +end + +assert 'UnboundMethod#bind_call' do + m = Array.instance_method(:size) + assert_equal(:size, m.name) + assert_equal(0, m.bind_call([])) + assert_equal(1, m.bind_call([1])) + assert_equal(2, m.bind_call([1,2])) +end |
