diff options
Diffstat (limited to 'proc.c')
| -rw-r--r-- | proc.c | 1266 |
1 files changed, 831 insertions, 435 deletions
@@ -10,34 +10,31 @@ **********************************************************************/ #include "eval_intern.h" -#include "gc.h" #include "internal.h" #include "internal/class.h" #include "internal/error.h" #include "internal/eval.h" +#include "internal/gc.h" +#include "internal/hash.h" #include "internal/object.h" #include "internal/proc.h" #include "internal/symbol.h" #include "method.h" #include "iseq.h" #include "vm_core.h" +#include "ractor_core.h" #include "yjit.h" -#if !defined(__GNUC__) || __GNUC__ < 5 || defined(__MINGW32__) -# define NO_CLOBBERED(v) (*(volatile VALUE *)&(v)) -#else -# define NO_CLOBBERED(v) (v) -#endif - -#define UPDATE_TYPED_REFERENCE(_type, _ref) *(_type*)&_ref = (_type)rb_gc_location((VALUE)_ref) -#define UPDATE_REFERENCE(_ref) UPDATE_TYPED_REFERENCE(VALUE, _ref) - const rb_cref_t *rb_vm_cref_in_context(VALUE self, VALUE cbase); struct METHOD { const VALUE recv; const VALUE klass; + /* needed for #super_method */ const VALUE iclass; + /* Different than me->owner only for ZSUPER methods. + This is error-prone but unavoidable unless ZSUPER methods are removed. */ + const VALUE owner; const rb_method_entry_t * const me; /* for bound methods, `me' should be rb_callable_method_entry_t * */ }; @@ -52,87 +49,39 @@ static int method_arity(VALUE); static int method_min_max_arity(VALUE, int *max); static VALUE proc_binding(VALUE self); -#define attached id__attached__ - /* Proc */ #define IS_METHOD_PROC_IFUNC(ifunc) ((ifunc)->func == bmcall) -/* :FIXME: The way procs are cloned has been historically different from the - * way everything else are. @shyouhei is not sure for the intention though. - */ -#undef CLONESETUP -static inline void -CLONESETUP(VALUE clone, VALUE obj) -{ - RBIMPL_ASSERT_OR_ASSUME(! RB_SPECIAL_CONST_P(obj)); - RBIMPL_ASSERT_OR_ASSUME(! RB_SPECIAL_CONST_P(clone)); - - const VALUE flags = RUBY_FL_PROMOTED0 | RUBY_FL_PROMOTED1 | RUBY_FL_FINALIZE; - rb_obj_setup(clone, rb_singleton_class_clone(obj), - RB_FL_TEST_RAW(obj, ~flags)); - rb_singleton_class_attached(RBASIC_CLASS(clone), clone); - if (RB_FL_TEST(obj, RUBY_FL_EXIVAR)) rb_copy_generic_ivar(clone, obj); -} - -static void -block_mark(const struct rb_block *block) -{ - switch (vm_block_type(block)) { - case block_type_iseq: - case block_type_ifunc: - { - const struct rb_captured_block *captured = &block->as.captured; - RUBY_MARK_MOVABLE_UNLESS_NULL(captured->self); - RUBY_MARK_MOVABLE_UNLESS_NULL((VALUE)captured->code.val); - if (captured->ep && captured->ep[VM_ENV_DATA_INDEX_ENV] != Qundef /* cfunc_proc_t */) { - rb_gc_mark(VM_ENV_ENVVAL(captured->ep)); - } - } - break; - case block_type_symbol: - RUBY_MARK_MOVABLE_UNLESS_NULL(block->as.symbol); - break; - case block_type_proc: - RUBY_MARK_MOVABLE_UNLESS_NULL(block->as.proc); - break; - } -} - static void -block_compact(struct rb_block *block) +block_mark_and_move(struct rb_block *block) { switch (block->type) { case block_type_iseq: case block_type_ifunc: { struct rb_captured_block *captured = &block->as.captured; - captured->self = rb_gc_location(captured->self); - captured->code.val = rb_gc_location(captured->code.val); + rb_gc_mark_and_move(&captured->self); + rb_gc_mark_and_move(&captured->code.val); + if (captured->ep) { + rb_gc_mark_and_move((VALUE *)&captured->ep[VM_ENV_DATA_INDEX_ENV]); + } } break; case block_type_symbol: - block->as.symbol = rb_gc_location(block->as.symbol); + rb_gc_mark_and_move(&block->as.symbol); break; case block_type_proc: - block->as.proc = rb_gc_location(block->as.proc); + rb_gc_mark_and_move(&block->as.proc); break; } } static void -proc_compact(void *ptr) -{ - rb_proc_t *proc = ptr; - block_compact((struct rb_block *)&proc->block); -} - -static void -proc_mark(void *ptr) +proc_mark_and_move(void *ptr) { rb_proc_t *proc = ptr; - block_mark(&proc->block); - RUBY_MARK_LEAVE("proc"); + block_mark_and_move((struct rb_block *)&proc->block); } typedef struct { @@ -149,17 +98,19 @@ proc_memsize(const void *ptr) return sizeof(rb_proc_t); } -static const rb_data_type_t proc_data_type = { +const rb_data_type_t ruby_proc_data_type = { "proc", { - proc_mark, + proc_mark_and_move, RUBY_TYPED_DEFAULT_FREE, proc_memsize, - proc_compact, + proc_mark_and_move, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; +#define proc_data_type ruby_proc_data_type + VALUE rb_proc_alloc(VALUE klass) { @@ -178,8 +129,15 @@ static VALUE proc_clone(VALUE self) { VALUE procval = rb_proc_dup(self); - CLONESETUP(procval, self); - return procval; + return rb_obj_clone_setup(self, procval, Qnil); +} + +/* :nodoc: */ +static VALUE +proc_dup(VALUE self) +{ + VALUE procval = rb_proc_dup(self); + return rb_obj_dup_setup(self, procval); } /* @@ -300,28 +258,17 @@ static void binding_free(void *ptr) { RUBY_FREE_ENTER("binding"); - ruby_xfree(ptr); + SIZED_FREE((rb_binding_t *)ptr); RUBY_FREE_LEAVE("binding"); } static void -binding_mark(void *ptr) -{ - rb_binding_t *bind = ptr; - - RUBY_MARK_ENTER("binding"); - block_mark(&bind->block); - rb_gc_mark_movable(bind->pathobj); - RUBY_MARK_LEAVE("binding"); -} - -static void -binding_compact(void *ptr) +binding_mark_and_move(void *ptr) { rb_binding_t *bind = ptr; - block_compact((struct rb_block *)&bind->block); - UPDATE_REFERENCE(bind->pathobj); + block_mark_and_move((struct rb_block *)&bind->block); + rb_gc_mark_and_move((VALUE *)&bind->pathobj); } static size_t @@ -333,10 +280,10 @@ binding_memsize(const void *ptr) const rb_data_type_t ruby_binding_data_type = { "binding", { - binding_mark, + binding_mark_and_move, binding_free, binding_memsize, - binding_compact, + binding_mark_and_move, }, 0, 0, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY }; @@ -353,10 +300,8 @@ rb_binding_alloc(VALUE klass) return obj; } - -/* :nodoc: */ static VALUE -binding_dup(VALUE self) +binding_copy(VALUE self) { VALUE bindval = rb_binding_alloc(rb_cBinding); rb_binding_t *src, *dst; @@ -370,11 +315,16 @@ binding_dup(VALUE self) /* :nodoc: */ static VALUE +binding_dup(VALUE self) +{ + return rb_obj_dup_setup(self, binding_copy(self)); +} + +/* :nodoc: */ +static VALUE binding_clone(VALUE self) { - VALUE bindval = binding_dup(self); - CLONESETUP(bindval, self); - return bindval; + return rb_obj_clone_setup(self, binding_copy(self), Qnil); } VALUE @@ -388,16 +338,44 @@ rb_binding_new(void) * call-seq: * binding -> a_binding * - * Returns a +Binding+ object, describing the variable and + * Returns a Binding object, describing the variable and * method bindings at the point of call. This object can be used when - * calling +eval+ to execute the evaluated command in this - * environment. See also the description of class +Binding+. + * calling Binding#eval to execute the evaluated command in this + * environment, or extracting its local variables. * - * def get_binding(param) - * binding + * class User + * def initialize(name, position) + * @name = name + * @position = position + * end + * + * def get_binding + * binding + * end * end - * b = get_binding("hello") - * eval("param", b) #=> "hello" + * + * user = User.new('Joan', 'manager') + * template = '{name: @name, position: @position}' + * + * # evaluate template in context of the object + * eval(template, user.get_binding) + * #=> {name: "Joan", position: "manager"} + * + * Binding#local_variable_get can be used to access the variables + * whose names are reserved Ruby keywords: + * + * # This is valid parameter declaration, but `if` parameter can't + * # be accessed by name, because it is a reserved word. + * def validate(field, validation, if: nil) + * condition = binding.local_variable_get('if') + * return unless condition + * + * # ...Some implementation ... + * end + * + * validate(:name, :empty?, if: false) # skips validation + * validate(:name, :empty?, if: true) # performs validation + * */ static VALUE @@ -407,19 +385,31 @@ rb_f_binding(VALUE self) } /* - * call-seq: - * binding.eval(string [, filename [,lineno]]) -> obj + * call-seq: + * binding.eval(string, filename = default_filename, lineno = 1) -> obj * - * Evaluates the Ruby expression(s) in <em>string</em>, in the - * <em>binding</em>'s context. If the optional <em>filename</em> and - * <em>lineno</em> parameters are present, they will be used when - * reporting syntax errors. + * Evaluates the Ruby expression(s) in +string+ in the context of + * +self+. Returns the result of the last expression: * - * def get_binding(param) - * binding - * end + * def get_binding(param) = binding * b = get_binding("hello") * b.eval("param") #=> "hello" + * + * If the optional +filename+ is given, it will be used as the + * filename of the evaluation (for <tt>__FILE__</tt> and errors). + * Otherwise, it will default to <tt>(eval at __FILE__:__LINE__)</tt> + * where <tt>__FILE__</tt> and <tt>__LINE__</tt> are the filename and + * line number of the caller, respectively: + * + * b.eval("puts __FILE__") # => "(eval at test.rb:4)" + * b.eval("puts __FILE__", "foobar.rb") # => "foobar.rb" + * + * If the optional +lineno+ is given, it will be used as the + * line number of the evaluation (for <tt>__LINE__</tt> and errors). + * Otherwise, it will default to 1: + * + * b.eval("puts __LINE__") # => 1 + * b.eval("puts __LINE__", "foobar.rb", 10) # => 10 */ static VALUE @@ -433,7 +423,7 @@ bind_eval(int argc, VALUE *argv, VALUE bindval) } static const VALUE * -get_local_variable_ptr(const rb_env_t **envp, ID lid) +get_local_variable_ptr(const rb_env_t **envp, ID lid, bool search_outer) { const rb_env_t *env = *envp; do { @@ -443,11 +433,11 @@ get_local_variable_ptr(const rb_env_t **envp, ID lid) } const rb_iseq_t *iseq = env->iseq; - unsigned int i; VM_ASSERT(rb_obj_is_iseq((VALUE)iseq)); - for (i=0; i<ISEQ_BODY(iseq)->local_table_size; i++) { + const unsigned int local_table_size = ISEQ_BODY(iseq)->local_table_size; + for (unsigned int i=0; i<local_table_size; i++) { if (ISEQ_BODY(iseq)->local_table[i] == lid) { if (ISEQ_BODY(iseq)->local_iseq == iseq && ISEQ_BODY(iseq)->param.flags.has_block && @@ -460,7 +450,9 @@ get_local_variable_ptr(const rb_env_t **envp, ID lid) } *envp = env; - return &env->env[i]; + unsigned int last_lvar = env->env_size+VM_ENV_INDEX_LAST_LVAR + - 1 /* errinfo */; + return &env->env[last_lvar - (local_table_size - i)]; } } } @@ -468,7 +460,7 @@ get_local_variable_ptr(const rb_env_t **envp, ID lid) *envp = NULL; return NULL; } - } while ((env = rb_vm_env_prev_env(env)) != NULL); + } while (search_outer && (env = rb_vm_env_prev_env(env)) != NULL); *envp = NULL; return NULL; @@ -487,13 +479,13 @@ check_local_id(VALUE bindval, volatile VALUE *pname) if (lid) { if (!rb_is_local_id(lid)) { - rb_name_err_raise("wrong local variable name `%1$s' for %2$s", + rb_name_err_raise("wrong local variable name '%1$s' for %2$s", bindval, ID2SYM(lid)); } } else { if (!rb_is_local_name(name)) { - rb_name_err_raise("wrong local variable name `%1$s' for %2$s", + rb_name_err_raise("wrong local variable name '%1$s' for %2$s", bindval, name); } return 0; @@ -530,6 +522,18 @@ bind_local_variables(VALUE bindval) return rb_vm_env_local_variables(env); } +int +rb_numparam_id_p(ID id) +{ + return (tNUMPARAM_1 << ID_SCOPE_SHIFT) <= id && id < ((tNUMPARAM_1 + 9) << ID_SCOPE_SHIFT); +} + +int +rb_implicit_param_p(ID id) +{ + return id == idItImplicit || rb_numparam_id_p(id); +} + /* * call-seq: * binding.local_variable_get(symbol) -> obj @@ -556,17 +560,21 @@ bind_local_variable_get(VALUE bindval, VALUE sym) const rb_env_t *env; if (!lid) goto undefined; + if (rb_numparam_id_p(lid)) { + rb_name_err_raise("numbered parameter '%1$s' is not a local variable", + bindval, ID2SYM(lid)); + } GetBindingPtr(bindval, bind); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); - if ((ptr = get_local_variable_ptr(&env, lid)) != NULL) { + if ((ptr = get_local_variable_ptr(&env, lid, TRUE)) != NULL) { return *ptr; } sym = ID2SYM(lid); undefined: - rb_name_err_raise("local variable `%1$s' is not defined for %2$s", + rb_name_err_raise("local variable '%1$s' is not defined for %2$s", bindval, sym); UNREACHABLE_RETURN(Qundef); } @@ -605,10 +613,14 @@ bind_local_variable_set(VALUE bindval, VALUE sym, VALUE val) const rb_env_t *env; if (!lid) lid = rb_intern_str(sym); + if (rb_numparam_id_p(lid)) { + rb_name_err_raise("numbered parameter '%1$s' is not a local variable", + bindval, ID2SYM(lid)); + } GetBindingPtr(bindval, bind); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); - if ((ptr = get_local_variable_ptr(&env, lid)) == NULL) { + if ((ptr = get_local_variable_ptr(&env, lid, TRUE)) == NULL) { /* not found. create new env */ ptr = rb_binding_add_dynavars(bindval, bind, 1, &lid); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); @@ -648,10 +660,140 @@ bind_local_variable_defined_p(VALUE bindval, VALUE sym) const rb_env_t *env; if (!lid) return Qfalse; + if (rb_numparam_id_p(lid)) { + rb_name_err_raise("numbered parameter '%1$s' is not a local variable", + bindval, ID2SYM(lid)); + } + + GetBindingPtr(bindval, bind); + env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); + return RBOOL(get_local_variable_ptr(&env, lid, TRUE)); +} + +/* + * call-seq: + * binding.implicit_parameters -> Array + * + * Returns the names of numbered parameters and "it" parameter + * that are defined in the binding. + * + * def foo + * [42].each do + * it + * binding.implicit_parameters #=> [:it] + * end + * + * { k: 42 }.each do + * _2 + * binding.implicit_parameters #=> [:_1, :_2] + * end + * end + * + */ +static VALUE +bind_implicit_parameters(VALUE bindval) +{ + const rb_binding_t *bind; + const rb_env_t *env; + + GetBindingPtr(bindval, bind); + env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); + + if (get_local_variable_ptr(&env, idItImplicit, FALSE)) { + return rb_ary_new_from_args(1, ID2SYM(idIt)); + } + + env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); + return rb_vm_env_numbered_parameters(env); +} + +/* + * call-seq: + * binding.implicit_parameter_get(symbol) -> obj + * + * Returns the value of the numbered parameter or "it" parameter. + * + * def foo + * [42].each do + * it + * binding.implicit_parameter_get(:it) #=> 42 + * end + * + * { k: 42 }.each do + * _2 + * binding.implicit_parameter_get(:_1) #=> :k + * binding.implicit_parameter_get(:_2) #=> 42 + * end + * end + * + */ +static VALUE +bind_implicit_parameter_get(VALUE bindval, VALUE sym) +{ + ID lid = check_local_id(bindval, &sym); + const rb_binding_t *bind; + const VALUE *ptr; + const rb_env_t *env; + + if (lid == idIt) lid = idItImplicit; + + if (!lid || !rb_implicit_param_p(lid)) { + rb_name_err_raise("'%1$s' is not an implicit parameter", + bindval, sym); + } + + GetBindingPtr(bindval, bind); + + env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); + if ((ptr = get_local_variable_ptr(&env, lid, FALSE)) != NULL) { + return *ptr; + } + + if (lid == idItImplicit) lid = idIt; + rb_name_err_raise("implicit parameter '%1$s' is not defined for %2$s", bindval, ID2SYM(lid)); + UNREACHABLE_RETURN(Qundef); +} + +/* + * call-seq: + * binding.implicit_parameter_defined?(symbol) -> obj + * + * Returns +true+ if the numbered parameter or "it" parameter exists. + * + * def foo + * [42].each do + * it + * binding.implicit_parameter_defined?(:it) #=> true + * binding.implicit_parameter_defined?(:_1) #=> false + * end + * + * { k: 42 }.each do + * _2 + * binding.implicit_parameter_defined?(:_1) #=> true + * binding.implicit_parameter_defined?(:_2) #=> true + * binding.implicit_parameter_defined?(:_3) #=> false + * binding.implicit_parameter_defined?(:it) #=> false + * end + * end + * + */ +static VALUE +bind_implicit_parameter_defined_p(VALUE bindval, VALUE sym) +{ + ID lid = check_local_id(bindval, &sym); + const rb_binding_t *bind; + const rb_env_t *env; + + if (lid == idIt) lid = idItImplicit; + + if (!lid || !rb_implicit_param_p(lid)) { + rb_name_err_raise("'%1$s' is not an implicit parameter", + bindval, sym); + } GetBindingPtr(bindval, bind); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); - return RBOOL(get_local_variable_ptr(&env, lid)); + return RBOOL(get_local_variable_ptr(&env, lid, FALSE)); } /* @@ -709,6 +851,32 @@ cfunc_proc_new(VALUE klass, VALUE ifunc) return procval; } +VALUE +rb_func_proc_dup(VALUE src_obj) +{ + RUBY_ASSERT(rb_typeddata_is_instance_of(src_obj, &proc_data_type)); + + rb_proc_t *src_proc; + GetProcPtr(src_obj, src_proc); + RUBY_ASSERT(vm_block_type(&src_proc->block) == block_type_ifunc); + + cfunc_proc_t *proc; + VALUE proc_obj = TypedData_Make_Struct(rb_obj_class(src_obj), cfunc_proc_t, &proc_data_type, proc); + + memcpy(&proc->basic, src_proc, sizeof(rb_proc_t)); + RB_OBJ_WRITTEN(proc_obj, Qundef, proc->basic.block.as.captured.self); + RB_OBJ_WRITTEN(proc_obj, Qundef, proc->basic.block.as.captured.code.val); + + const VALUE *src_ep = src_proc->block.as.captured.ep; + VALUE *ep = *(VALUE **)&proc->basic.block.as.captured.ep = proc->env + VM_ENV_DATA_SIZE - 1; + ep[VM_ENV_DATA_INDEX_FLAGS] = src_ep[VM_ENV_DATA_INDEX_FLAGS]; + ep[VM_ENV_DATA_INDEX_ME_CREF] = src_ep[VM_ENV_DATA_INDEX_ME_CREF]; + ep[VM_ENV_DATA_INDEX_SPECVAL] = src_ep[VM_ENV_DATA_INDEX_SPECVAL]; + RB_OBJ_WRITE(proc_obj, &ep[VM_ENV_DATA_INDEX_ENV], src_ep[VM_ENV_DATA_INDEX_ENV]); + + return proc_obj; +} + static VALUE sym_proc_new(VALUE klass, VALUE sym) { @@ -725,11 +893,6 @@ sym_proc_new(VALUE klass, VALUE sym) struct vm_ifunc * rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc) { - union { - struct vm_ifunc_argc argc; - VALUE packed; - } arity; - if (min_argc < UNLIMITED_ARGUMENTS || #if SIZEOF_INT * 2 > SIZEOF_VALUE min_argc >= (int)(1U << (SIZEOF_VALUE * CHAR_BIT) / 2) || @@ -746,20 +909,21 @@ rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int m rb_raise(rb_eRangeError, "maximum argument number out of range: %d", max_argc); } - arity.argc.min = min_argc; - arity.argc.max = max_argc; - VALUE ret = rb_imemo_new(imemo_ifunc, (VALUE)func, (VALUE)data, arity.packed, 0); - return (struct vm_ifunc *)ret; -} + rb_execution_context_t *ec = GET_EC(); -MJIT_FUNC_EXPORTED VALUE -rb_func_proc_new(rb_block_call_func_t func, VALUE val) -{ - struct vm_ifunc *ifunc = rb_vm_ifunc_proc_new(func, (void *)val); - return cfunc_proc_new(rb_cProc, (VALUE)ifunc); + struct vm_ifunc *ifunc = IMEMO_NEW(struct vm_ifunc, imemo_ifunc, (VALUE)rb_vm_svar_lep(ec, ec->cfp)); + + rb_gc_register_pinning_obj((VALUE)ifunc); + + ifunc->func = func; + ifunc->data = data; + ifunc->argc.min = min_argc; + ifunc->argc.max = max_argc; + + return ifunc; } -MJIT_FUNC_EXPORTED VALUE +VALUE rb_func_lambda_new(rb_block_call_func_t func, VALUE val, int min_argc, int max_argc) { struct vm_ifunc *ifunc = rb_vm_ifunc_new(func, (void *)val, min_argc, max_argc); @@ -769,7 +933,7 @@ rb_func_lambda_new(rb_block_call_func_t func, VALUE val, int min_argc, int max_a static const char proc_without_block[] = "tried to create Proc object without a block"; static VALUE -proc_new(VALUE klass, int8_t is_lambda, int8_t kernel) +proc_new(VALUE klass, int8_t is_lambda) { VALUE procval; const rb_execution_context_t *ec = GET_EC(); @@ -802,16 +966,8 @@ proc_new(VALUE klass, int8_t is_lambda, int8_t kernel) break; case block_handler_type_ifunc: - return rb_vm_make_proc_lambda(ec, VM_BH_TO_CAPT_BLOCK(block_handler), klass, is_lambda); case block_handler_type_iseq: - { - const struct rb_captured_block *captured = VM_BH_TO_CAPT_BLOCK(block_handler); - rb_control_frame_t *last_ruby_cfp = rb_vm_get_ruby_level_next_cfp(ec, cfp); - if (is_lambda && last_ruby_cfp && vm_cfp_forwarded_bh_p(last_ruby_cfp, block_handler)) { - is_lambda = false; - } - return rb_vm_make_proc_lambda(ec, captured, klass, is_lambda); - } + return rb_vm_make_proc_lambda(ec, VM_BH_TO_CAPT_BLOCK(block_handler), klass, is_lambda); } VM_UNREACHABLE(proc_new); return Qnil; @@ -834,7 +990,7 @@ proc_new(VALUE klass, int8_t is_lambda, int8_t kernel) static VALUE rb_proc_s_new(int argc, VALUE *argv, VALUE klass) { - VALUE block = proc_new(klass, FALSE, FALSE); + VALUE block = proc_new(klass, FALSE); rb_obj_call_init_kw(block, argc, argv, RB_PASS_CALLED_KEYWORDS); return block; @@ -843,7 +999,7 @@ rb_proc_s_new(int argc, VALUE *argv, VALUE klass) VALUE rb_block_proc(void) { - return proc_new(rb_cProc, FALSE, FALSE); + return proc_new(rb_cProc, FALSE); } /* @@ -856,41 +1012,44 @@ rb_block_proc(void) static VALUE f_proc(VALUE _) { - return proc_new(rb_cProc, FALSE, TRUE); + return proc_new(rb_cProc, FALSE); } VALUE rb_block_lambda(void) { - return proc_new(rb_cProc, TRUE, FALSE); + return proc_new(rb_cProc, TRUE); } static void -f_lambda_warn(void) +f_lambda_filter_non_literal(void) { rb_control_frame_t *cfp = GET_EC()->cfp; VALUE block_handler = rb_vm_frame_block_handler(cfp); - if (block_handler != VM_BLOCK_HANDLER_NONE) { - switch (vm_block_handler_type(block_handler)) { - case block_handler_type_iseq: - if (RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)->ep == VM_BH_TO_ISEQ_BLOCK(block_handler)->ep) { - return; - } - break; - case block_handler_type_symbol: + if (block_handler == VM_BLOCK_HANDLER_NONE) { + // no block error raised else where + return; + } + + switch (vm_block_handler_type(block_handler)) { + case block_handler_type_iseq: + if (RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)->ep == VM_BH_TO_ISEQ_BLOCK(block_handler)->ep) { + return; + } + break; + case block_handler_type_symbol: + return; + case block_handler_type_proc: + if (rb_proc_lambda_p(VM_BH_TO_PROC(block_handler))) { return; - case block_handler_type_proc: - if (rb_proc_lambda_p(VM_BH_TO_PROC(block_handler))) { - return; - } - break; - case block_handler_type_ifunc: - break; } + break; + case block_handler_type_ifunc: + break; } - rb_warn_deprecated("lambda without a literal block", "the proc without lambda"); + rb_raise(rb_eArgError, "the lambda method requires a literal block"); } /* @@ -904,7 +1063,7 @@ f_lambda_warn(void) static VALUE f_lambda(VALUE _) { - f_lambda_warn(); + f_lambda_filter_non_literal(); return rb_block_lambda(); } @@ -926,13 +1085,12 @@ f_lambda(VALUE _) * Document-method: Proc#yield * * call-seq: - * prc.call(params,...) -> obj - * prc[params,...] -> obj - * prc.(params,...) -> obj - * prc.yield(params,...) -> obj + * call(...) -> obj + * self[...] -> obj + * yield(...) -> obj * - * Invokes the block, setting the block's parameters to the values in - * <i>params</i> using something close to method calling semantics. + * Invokes the block, setting the block's parameters to the arguments + * using something close to method calling semantics. * Returns the value of the last expression evaluated in the block. * * a_proc = Proc.new {|scalar, *values| values.map {|value| value*scalar } } @@ -986,7 +1144,12 @@ rb_proc_call_kw(VALUE self, VALUE args, int kw_splat) VALUE vret; rb_proc_t *proc; int argc = check_argc(RARRAY_LEN(args)); - const VALUE *argv = RARRAY_CONST_PTR(args); + + // rb_vm_invoke_proc may end up modifying argv as part of calling and so we + // must use RARRAY_PTR, which marks the array as WB_UNPROTECTED instead of + // RARRAY_CONST_PTR. Unfortunately this is worse for GC. + // See invoke_block_from_c_proc + VALUE *argv = RARRAY_PTR(args); GetProcPtr(self, proc); vret = rb_vm_invoke_proc(GET_EC(), proc, argc, argv, kw_splat, VM_BLOCK_HANDLER_NONE); @@ -1079,7 +1242,7 @@ rb_iseq_min_max_arity(const rb_iseq_t *iseq, int *max) { *max = ISEQ_BODY(iseq)->param.flags.has_rest == FALSE ? ISEQ_BODY(iseq)->param.lead_num + ISEQ_BODY(iseq)->param.opt_num + ISEQ_BODY(iseq)->param.post_num + - (ISEQ_BODY(iseq)->param.flags.has_kw == TRUE || ISEQ_BODY(iseq)->param.flags.has_kwrest == TRUE) + (ISEQ_BODY(iseq)->param.flags.has_kw == TRUE || ISEQ_BODY(iseq)->param.flags.has_kwrest == TRUE || ISEQ_BODY(iseq)->param.flags.forwardable == TRUE) : UNLIMITED_ARGUMENTS; return ISEQ_BODY(iseq)->param.lead_num + ISEQ_BODY(iseq)->param.post_num + (ISEQ_BODY(iseq)->param.flags.has_kw && ISEQ_BODY(iseq)->param.keyword->required_num > 0); } @@ -1175,10 +1338,10 @@ rb_block_pair_yield_optimizable(void) min = rb_vm_block_min_max_arity(&block, &max); switch (vm_block_type(&block)) { - case block_handler_type_symbol: + case block_type_symbol: return 0; - case block_handler_type_proc: + case block_type_proc: { VALUE procval = block_handler; rb_proc_t *proc; @@ -1188,6 +1351,12 @@ rb_block_pair_yield_optimizable(void) return min > 1; } + case block_type_ifunc: + { + const struct vm_ifunc *ifunc = block.as.captured.code.ifunc; + if (ifunc->flags & IFUNC_YIELD_OPTIMIZABLE) return 1; + } + default: return min > 1; } @@ -1209,10 +1378,10 @@ rb_block_arity(void) block_setup(&block, block_handler); switch (vm_block_type(&block)) { - case block_handler_type_symbol: + case block_type_symbol: return -1; - case block_handler_type_proc: + case block_type_proc: return rb_proc_arity(block_handler); default: @@ -1273,10 +1442,10 @@ rb_proc_get_iseq(VALUE self, int *is_proc) } /* call-seq: - * prc == other -> true or false - * prc.eql?(other) -> true or false + * self == other -> true or false + * eql?(other) -> true or false * - * Two procs are the same if, and only if, they were created from the same code block. + * Returns whether +self+ and +other+ were created from the same code block: * * def return_block(&block) * block @@ -1333,12 +1502,17 @@ proc_eq(VALUE self, VALUE other) } break; case block_type_ifunc: - if (self_block->as.captured.ep != \ - other_block->as.captured.ep || - self_block->as.captured.code.ifunc != \ + if (self_block->as.captured.code.ifunc != \ other_block->as.captured.code.ifunc) { return Qfalse; } + + if (memcmp( + ((cfunc_proc_t *)self_proc)->env, + ((cfunc_proc_t *)other_proc)->env, + sizeof(((cfunc_proc_t *)self_proc)->env))) { + return Qfalse; + } break; case block_type_proc: if (self_block->as.proc != other_block->as.proc) { @@ -1363,12 +1537,12 @@ iseq_location(const rb_iseq_t *iseq) if (!iseq) return Qnil; rb_iseq_check(iseq); loc[0] = rb_iseq_path(iseq); - loc[1] = ISEQ_BODY(iseq)->location.first_lineno; + loc[1] = RB_INT2NUM(ISEQ_BODY(iseq)->location.first_lineno); return rb_ary_new4(2, loc); } -MJIT_FUNC_EXPORTED VALUE +VALUE rb_iseq_location(const rb_iseq_t *iseq) { return iseq_location(iseq); @@ -1460,11 +1634,36 @@ rb_hash_proc(st_index_t hash, VALUE prc) { rb_proc_t *proc; GetProcPtr(prc, proc); - hash = rb_hash_uint(hash, (st_index_t)proc->block.as.captured.code.val); - hash = rb_hash_uint(hash, (st_index_t)proc->block.as.captured.self); - return rb_hash_uint(hash, (st_index_t)proc->block.as.captured.ep); + + switch (vm_block_type(&proc->block)) { + case block_type_iseq: + hash = rb_st_hash_uint(hash, (st_index_t)proc->block.as.captured.code.iseq->body); + break; + case block_type_ifunc: + hash = rb_st_hash_uint(hash, (st_index_t)proc->block.as.captured.code.ifunc->func); + hash = rb_st_hash_uint(hash, (st_index_t)proc->block.as.captured.code.ifunc->data); + break; + case block_type_symbol: + hash = rb_st_hash_uint(hash, rb_any_hash(proc->block.as.symbol)); + break; + case block_type_proc: + hash = rb_st_hash_uint(hash, rb_any_hash(proc->block.as.proc)); + break; + default: + rb_bug("rb_hash_proc: unknown block type %d", vm_block_type(&proc->block)); + } + + /* ifunc procs have their own allocated ep. If an ifunc is duplicated, they + * will point to different ep but they should return the same hash code, so + * we cannot include the ep in the hash. */ + if (vm_block_type(&proc->block) != block_type_ifunc) { + hash = rb_hash_uint(hash, (st_index_t)proc->block.as.captured.ep); + } + + return hash; } +static VALUE sym_proc_cache = Qfalse; /* * call-seq: @@ -1480,32 +1679,36 @@ rb_hash_proc(st_index_t hash, VALUE prc) * */ -MJIT_FUNC_EXPORTED VALUE +VALUE rb_sym_to_proc(VALUE sym) { - static VALUE sym_proc_cache = Qfalse; enum {SYM_PROC_CACHE_SIZE = 67}; - VALUE proc; - long index; - ID id; - if (!sym_proc_cache) { - sym_proc_cache = rb_ary_hidden_new(SYM_PROC_CACHE_SIZE * 2); - rb_gc_register_mark_object(sym_proc_cache); - rb_ary_store(sym_proc_cache, SYM_PROC_CACHE_SIZE*2 - 1, Qnil); - } + if (rb_ractor_main_p()) { + if (!sym_proc_cache) { + sym_proc_cache = rb_ary_hidden_new(SYM_PROC_CACHE_SIZE); + rb_ary_store(sym_proc_cache, SYM_PROC_CACHE_SIZE - 1, Qnil); + } + + ID id = SYM2ID(sym); + long index = (id % SYM_PROC_CACHE_SIZE); + VALUE procval = RARRAY_AREF(sym_proc_cache, index); + if (RTEST(procval)) { + rb_proc_t *proc; + GetProcPtr(procval, proc); - id = SYM2ID(sym); - index = (id % SYM_PROC_CACHE_SIZE) << 1; + if (proc->block.as.symbol == sym) { + return procval; + } + } - if (RARRAY_AREF(sym_proc_cache, index) == sym) { - return RARRAY_AREF(sym_proc_cache, index + 1); + procval = sym_proc_new(rb_cProc, sym); + RARRAY_ASET(sym_proc_cache, index, procval); + + return RB_GC_GUARD(procval); } else { - proc = sym_proc_new(rb_cProc, ID2SYM(id)); - RARRAY_ASET(sym_proc_cache, index, sym); - RARRAY_ASET(sym_proc_cache, index + 1, proc); - return proc; + return sym_proc_new(rb_cProc, sym); } } @@ -1544,7 +1747,7 @@ rb_block_to_s(VALUE self, const struct rb_block *block, const char *additional_i const rb_iseq_t *iseq = rb_iseq_check(block->as.captured.code.iseq); rb_str_catf(str, "%p %"PRIsVALUE":%d", (void *)self, rb_iseq_path(iseq), - FIX2INT(ISEQ_BODY(iseq)->location.first_lineno)); + ISEQ_BODY(iseq)->location.first_lineno); } break; case block_type_symbol: @@ -1591,40 +1794,25 @@ proc_to_proc(VALUE self) } static void -bm_mark(void *ptr) -{ - struct METHOD *data = ptr; - rb_gc_mark_movable(data->recv); - rb_gc_mark_movable(data->klass); - rb_gc_mark_movable(data->iclass); - rb_gc_mark_movable((VALUE)data->me); -} - -static void -bm_compact(void *ptr) +bm_mark_and_move(void *ptr) { struct METHOD *data = ptr; - UPDATE_REFERENCE(data->recv); - UPDATE_REFERENCE(data->klass); - UPDATE_REFERENCE(data->iclass); - UPDATE_TYPED_REFERENCE(rb_method_entry_t *, data->me); -} - -static size_t -bm_memsize(const void *ptr) -{ - return sizeof(struct METHOD); + rb_gc_mark_and_move((VALUE *)&data->recv); + rb_gc_mark_and_move((VALUE *)&data->klass); + rb_gc_mark_and_move((VALUE *)&data->iclass); + rb_gc_mark_and_move((VALUE *)&data->owner); + rb_gc_mark_and_move_ptr((rb_method_entry_t **)&data->me); } static const rb_data_type_t method_data_type = { "method", { - bm_mark, + bm_mark_and_move, RUBY_TYPED_DEFAULT_FREE, - bm_memsize, - bm_compact, + NULL, // No external memory to report, + bm_mark_and_move, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE | RUBY_TYPED_FROZEN_SHAREABLE_NO_REC }; VALUE @@ -1639,7 +1827,7 @@ respond_to_missing_p(VALUE klass, VALUE obj, VALUE sym, int scope) /* TODO: merge with obj_respond_to() */ ID rmiss = idRespond_to_missing; - if (obj == Qundef) return 0; + if (UNDEF_P(obj)) return 0; if (rb_method_basic_definition_p(klass, rmiss)) return 0; return RTEST(rb_funcall(obj, rmiss, 2, sym, RBOOL(!scope))); } @@ -1655,6 +1843,7 @@ mnew_missing(VALUE klass, VALUE obj, ID id, VALUE mclass) RB_OBJ_WRITE(method, &data->recv, obj); RB_OBJ_WRITE(method, &data->klass, klass); + RB_OBJ_WRITE(method, &data->owner, klass); def = ZALLOC(rb_method_definition_t); def->type = VM_METHOD_TYPE_MISSING; @@ -1676,14 +1865,18 @@ mnew_missing_by_name(VALUE klass, VALUE obj, VALUE *name, int scope, VALUE mclas return mnew_missing(klass, obj, SYM2ID(vid), mclass); } +VALUE rb_zsuper_to_super(int argc, VALUE *argv, VALUE self); + static VALUE mnew_internal(const rb_method_entry_t *me, VALUE klass, VALUE iclass, VALUE obj, ID id, VALUE mclass, int scope, int error) { struct METHOD *data; VALUE method; + const rb_method_entry_t *original_me = me; rb_method_visibility_t visi = METHOD_VISI_UNDEF; + again: if (UNDEFINED_METHOD_ENTRY_P(me)) { if (respond_to_missing_p(klass, obj, ID2SYM(id), scope)) { return mnew_missing(klass, obj, id, mclass); @@ -1699,12 +1892,33 @@ mnew_internal(const rb_method_entry_t *me, VALUE klass, VALUE iclass, rb_print_inaccessible(klass, id, visi); } } + if (me->def->type == VM_METHOD_TYPE_ZSUPER || + (me->def->type == VM_METHOD_TYPE_CFUNC && me->def->body.cfunc.func == (rb_cfunc_t)rb_zsuper_to_super)) { + if (me->def->type == VM_METHOD_TYPE_ZSUPER && me->defined_class) { + VALUE klass = RCLASS_SUPER(RCLASS_ORIGIN(me->defined_class)); + id = me->def->original_id; + me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(klass, id, &iclass); + } + else { + VALUE klass = RCLASS_SUPER(RCLASS_ORIGIN(me->owner)); + id = me->def->original_id; + me = rb_method_entry_without_refinements(klass, id, &iclass); + } + goto again; + } method = TypedData_Make_Struct(mclass, struct METHOD, &method_data_type, data); - RB_OBJ_WRITE(method, &data->recv, obj); - RB_OBJ_WRITE(method, &data->klass, klass); + if (UNDEF_P(obj)) { + RB_OBJ_WRITE(method, &data->recv, Qundef); + RB_OBJ_WRITE(method, &data->klass, Qundef); + } + else { + RB_OBJ_WRITE(method, &data->recv, obj); + RB_OBJ_WRITE(method, &data->klass, klass); + } RB_OBJ_WRITE(method, &data->iclass, iclass); + RB_OBJ_WRITE(method, &data->owner, original_me->owner); RB_OBJ_WRITE(method, &data->me, me); return method; @@ -1723,7 +1937,7 @@ mnew_callable(VALUE klass, VALUE obj, ID id, VALUE mclass, int scope) const rb_method_entry_t *me; VALUE iclass = Qnil; - ASSUME(obj != Qundef); + ASSUME(!UNDEF_P(obj)); me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(klass, id, &iclass); return mnew_from_me(me, klass, iclass, obj, id, mclass, scope); } @@ -1738,27 +1952,6 @@ mnew_unbound(VALUE klass, ID id, VALUE mclass, int scope) return mnew_from_me(me, klass, iclass, Qundef, id, mclass, scope); } -static const rb_method_entry_t* -zsuper_resolve(const rb_method_entry_t *me) -{ - const rb_method_entry_t *super_me; - while (me->def->type == VM_METHOD_TYPE_ZSUPER) { - VALUE defined_class = me->defined_class ? me->defined_class : me->owner; - VALUE super_class = RCLASS_SUPER(RCLASS_ORIGIN(defined_class)); - if (!super_class) { - break; - } - ID id = me->def->original_id; - VALUE iclass; - super_me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, id, &iclass); - if (!super_me) { - break; - } - me = super_me; - } - return me; -} - static inline VALUE method_entry_defined_class(const rb_method_entry_t *me) { @@ -1770,7 +1963,7 @@ method_entry_defined_class(const rb_method_entry_t *me) * * Document-class: Method * - * Method objects are created by Object#method, and are associated + * +Method+ objects are created by Object#method, and are associated * with a particular object (not just with a class). They may be * used to invoke the method within the object, and as a block * associated with an iterator. They may also be unbound from one @@ -1796,10 +1989,9 @@ method_entry_defined_class(const rb_method_entry_t *me) /* * call-seq: - * meth.eql?(other_meth) -> true or false - * meth == other_meth -> true or false + * self == other -> true or false * - * Two method objects are equal if they are bound to the same + * Returns whether +self+ and +other+ are bound to the same * object and refer to the same method definition and the classes * defining the methods are the same class or module. */ @@ -1816,16 +2008,15 @@ method_eq(VALUE method, VALUE other) return Qfalse; Check_TypedStruct(method, &method_data_type); - m1 = (struct METHOD *)DATA_PTR(method); - m2 = (struct METHOD *)DATA_PTR(other); + m1 = (struct METHOD *)RTYPEDDATA_GET_DATA(method); + m2 = (struct METHOD *)RTYPEDDATA_GET_DATA(other); - const rb_method_entry_t *m1_me = zsuper_resolve(m1->me); - const rb_method_entry_t *m2_me = zsuper_resolve(m2->me); + klass1 = method_entry_defined_class(m1->me); + klass2 = method_entry_defined_class(m2->me); + if (RB_TYPE_P(klass1, T_ICLASS)) klass1 = RBASIC_CLASS(klass1); + if (RB_TYPE_P(klass2, T_ICLASS)) klass2 = RBASIC_CLASS(klass2); - klass1 = method_entry_defined_class(m1_me); - klass2 = method_entry_defined_class(m2_me); - - if (!rb_method_entry_eq(m1_me, m2_me) || + if (!rb_method_entry_eq(m1->me, m2->me) || klass1 != klass2 || m1->klass != m2->klass || m1->recv != m2->recv) { @@ -1837,6 +2028,22 @@ method_eq(VALUE method, VALUE other) /* * call-seq: + * meth.eql?(other_meth) -> true or false + * meth == other_meth -> true or false + * + * Two unbound method objects are equal if they refer to the same + * method definition. + * + * Array.instance_method(:each_slice) == Enumerable.instance_method(:each_slice) + * #=> true + * + * Array.instance_method(:sum) == Enumerable.instance_method(:sum) + * #=> false, Array redefines the method for efficiency + */ +#define unbound_method_eq method_eq + +/* + * call-seq: * meth.hash -> integer * * Returns a hash value corresponding to the method object. @@ -1877,8 +2084,9 @@ method_unbind(VALUE obj) method = TypedData_Make_Struct(rb_cUnboundMethod, struct METHOD, &method_data_type, data); RB_OBJ_WRITE(method, &data->recv, Qundef); - RB_OBJ_WRITE(method, &data->klass, orig->klass); + RB_OBJ_WRITE(method, &data->klass, Qundef); RB_OBJ_WRITE(method, &data->iclass, orig->iclass); + RB_OBJ_WRITE(method, &data->owner, orig->me->owner); RB_OBJ_WRITE(method, &data->me, rb_method_entry_clone(orig->me)); return method; @@ -1963,18 +2171,38 @@ method_owner(VALUE obj) { struct METHOD *data; TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data); - return data->me->owner; + return data->owner; +} + +/* + * call-seq: + * meth.box -> box or nil + * + * Returns the Ruby::Box where +meth+ is defined in. + */ +static VALUE +method_box(VALUE obj) +{ + struct METHOD *data; + const rb_box_t *box; + + TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data); + box = data->me->def->box; + if (!box) return Qnil; + if (box->box_object) return box->box_object; + rb_bug("Unexpected box on the method definition: %p", (void*) box); + UNREACHABLE_RETURN(Qnil); } void rb_method_name_error(VALUE klass, VALUE str) { -#define MSG(s) rb_fstring_lit("undefined method `%1$s' for"s" `%2$s'") +#define MSG(s) rb_fstring_lit("undefined method '%1$s' for"s" '%2$s'") VALUE c = klass; VALUE s = Qundef; - if (FL_TEST(c, FL_SINGLETON)) { - VALUE obj = rb_ivar_get(klass, attached); + if (RCLASS_SINGLETON_P(c)) { + VALUE obj = RCLASS_ATTACHED_OBJECT(klass); switch (BUILTIN_TYPE(obj)) { case T_MODULE: @@ -1988,7 +2216,7 @@ rb_method_name_error(VALUE klass, VALUE str) else if (RB_TYPE_P(c, T_MODULE)) { s = MSG(" module"); } - if (s == Qundef) { + if (UNDEF_P(s)) { s = MSG(" class"); } rb_name_err_raise_str(s, c, str); @@ -2015,7 +2243,7 @@ obj_method(VALUE obj, VALUE vid, int scope) * obj.method(sym) -> method * * Looks up the named method as a receiver in <i>obj</i>, returning a - * Method object (or raising NameError). The Method object acts as a + * +Method+ object (or raising NameError). The +Method+ object acts as a * closure in <i>obj</i>'s object instance, so instance variables and * the value of <code>self</code> remain available. * @@ -2036,7 +2264,7 @@ obj_method(VALUE obj, VALUE vid, int scope) * m = l.method("hello") * m.call #=> "Hello, @iv = Fred" * - * Note that Method implements <code>to_proc</code> method, which + * Note that +Method+ implements <code>to_proc</code> method, which * means it can be used with iterators. * * [ 1, 2, 3 ].each(&method(:puts)) # => prints 3 lines to stdout @@ -2068,6 +2296,19 @@ rb_obj_public_method(VALUE obj, VALUE vid) return obj_method(obj, vid, TRUE); } +static VALUE +rb_obj_singleton_method_lookup(VALUE arg) +{ + VALUE *args = (VALUE *)arg; + return rb_obj_method(args[0], args[1]); +} + +static VALUE +rb_obj_singleton_method_lookup_fail(VALUE arg1, VALUE arg2) +{ + return Qfalse; +} + /* * call-seq: * obj.singleton_method(sym) -> method @@ -2095,13 +2336,13 @@ rb_obj_public_method(VALUE obj, VALUE vid) VALUE rb_obj_singleton_method(VALUE obj, VALUE vid) { - VALUE klass = rb_singleton_class_get(obj); + VALUE sc = rb_singleton_class_get(obj); + VALUE klass; ID id = rb_check_id(&vid); - if (NIL_P(klass)) { - /* goto undef; */ - } - else if (NIL_P(klass = RCLASS_ORIGIN(klass))) { + if (NIL_P(sc) || + NIL_P(klass = RCLASS_ORIGIN(sc)) || + !NIL_P(rb_special_singleton_class(obj))) { /* goto undef; */ } else if (! id) { @@ -2110,22 +2351,27 @@ rb_obj_singleton_method(VALUE obj, VALUE vid) /* else goto undef; */ } else { - const rb_method_entry_t *me = rb_method_entry_at(klass, id); - vid = ID2SYM(id); - - if (UNDEFINED_METHOD_ENTRY_P(me)) { - /* goto undef; */ - } - else if (UNDEFINED_REFINED_METHOD_P(me->def)) { - /* goto undef; */ - } - else { - return mnew_from_me(me, klass, klass, obj, id, rb_cMethod, FALSE); + VALUE args[2] = {obj, vid}; + VALUE ruby_method = rb_rescue(rb_obj_singleton_method_lookup, (VALUE)args, rb_obj_singleton_method_lookup_fail, Qfalse); + if (ruby_method) { + struct METHOD *method = (struct METHOD *)RTYPEDDATA_GET_DATA(ruby_method); + VALUE lookup_class = RBASIC_CLASS(obj); + VALUE stop_class = rb_class_superclass(sc); + VALUE method_class = method->iclass; + + /* Determine if method is in singleton class, or module included in or prepended to it */ + do { + if (lookup_class == method_class) { + return ruby_method; + } + lookup_class = RCLASS_SUPER(lookup_class); + } while (lookup_class && lookup_class != stop_class); } } /* undef: */ - rb_name_err_raise("undefined singleton method `%1$s' for `%2$s'", + vid = ID2SYM(id); + rb_name_err_raise("undefined singleton method '%1$s' for '%2$s'", obj, vid); UNREACHABLE_RETURN(Qundef); } @@ -2136,29 +2382,29 @@ rb_obj_singleton_method(VALUE obj, VALUE vid) * * Returns an +UnboundMethod+ representing the given * instance method in _mod_. + * See +UnboundMethod+ about how to utilize it * - * class Interpreter - * def do_a() print "there, "; end - * def do_d() print "Hello "; end - * def do_e() print "!\n"; end - * def do_v() print "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) - * string.each_char {|b| Dispatcher[b].bind(self).call } - * end - * end + * class Person + * def initialize(name) + * @name = name + * end + * + * def hi + * puts "Hi, I'm #{@name}!" + * end + * end * - * interpreter = Interpreter.new - * interpreter.interpret('dave') + * dave = Person.new('Dave') + * thomas = Person.new('Thomas') + * + * hi = Person.instance_method(:hi) + * hi.bind_call(dave) + * hi.bind_call(thomas) * * <em>produces:</em> * - * Hello there, Dave! + * Hi, I'm Dave! + * Hi, I'm Thomas! */ static VALUE @@ -2220,10 +2466,10 @@ rb_mod_define_method_with_visibility(int argc, VALUE *argv, VALUE mod, const str if (!id) id = rb_to_id(name); if (is_method) { - struct METHOD *method = (struct METHOD *)DATA_PTR(body); + struct METHOD *method = (struct METHOD *)RTYPEDDATA_GET_DATA(body); if (method->me->owner != mod && !RB_TYPE_P(method->me->owner, T_MODULE) && !RTEST(rb_class_inherited_p(mod, method->me->owner))) { - if (FL_TEST(method->me->owner, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(method->me->owner)) { rb_raise(rb_eTypeError, "can't bind singleton method to a different class"); } @@ -2360,17 +2606,7 @@ rb_obj_define_method(int argc, VALUE *argv, VALUE obj) static VALUE top_define_method(int argc, VALUE *argv, VALUE obj) { - rb_thread_t *th = GET_THREAD(); - VALUE klass; - - klass = th->top_wrapper; - if (klass) { - rb_warning("main.define_method in the wrapped load is effective only in wrapper module"); - } - else { - klass = rb_cObject; - } - return rb_mod_define_method(argc, argv, klass); + return rb_mod_define_method(argc, argv, rb_top_main_class("define_method")); } /* @@ -2397,63 +2633,62 @@ method_clone(VALUE self) struct METHOD *orig, *data; TypedData_Get_Struct(self, struct METHOD, &method_data_type, orig); - clone = TypedData_Make_Struct(CLASS_OF(self), struct METHOD, &method_data_type, data); - CLONESETUP(clone, self); + clone = TypedData_Make_Struct(rb_obj_class(self), struct METHOD, &method_data_type, data); + rb_obj_clone_setup(self, clone, Qnil); RB_OBJ_WRITE(clone, &data->recv, orig->recv); RB_OBJ_WRITE(clone, &data->klass, orig->klass); RB_OBJ_WRITE(clone, &data->iclass, orig->iclass); + RB_OBJ_WRITE(clone, &data->owner, orig->owner); RB_OBJ_WRITE(clone, &data->me, rb_method_entry_clone(orig->me)); return clone; } -/* Document-method: Method#=== - * - * call-seq: - * method === obj -> result_of_method - * - * Invokes the method with +obj+ as the parameter like #call. - * This allows a method object to be the target of a +when+ clause - * in a case statement. - * - * require 'prime' - * - * case 1373 - * when Prime.method(:prime?) - * # ... - * end - */ - +/* :nodoc: */ +static VALUE +method_dup(VALUE self) +{ + VALUE clone; + struct METHOD *orig, *data; -/* Document-method: Method#[] - * - * call-seq: - * meth[args, ...] -> obj - * - * Invokes the <i>meth</i> with the specified arguments, returning the - * method's return value, like #call. - * - * m = 12.method("+") - * m[3] #=> 15 - * m[20] #=> 32 - */ + TypedData_Get_Struct(self, struct METHOD, &method_data_type, orig); + clone = TypedData_Make_Struct(rb_obj_class(self), struct METHOD, &method_data_type, data); + rb_obj_dup_setup(self, clone); + RB_OBJ_WRITE(clone, &data->recv, orig->recv); + RB_OBJ_WRITE(clone, &data->klass, orig->klass); + RB_OBJ_WRITE(clone, &data->iclass, orig->iclass); + RB_OBJ_WRITE(clone, &data->owner, orig->owner); + RB_OBJ_WRITE(clone, &data->me, rb_method_entry_clone(orig->me)); + return clone; +} /* * call-seq: - * meth.call(args, ...) -> obj + * call(...) -> obj + * self[...] -> obj + * self === obj -> result_of_method * - * Invokes the <i>meth</i> with the specified arguments, returning the + * Invokes +self+ with the specified arguments, returning the * method's return value. * * m = 12.method("+") * m.call(3) #=> 15 * m.call(20) #=> 32 + * + * Using Method#=== allows a method object to be the target of a +when+ clause + * in a case statement. + * + * require 'prime' + * + * case 1373 + * when Prime.method(:prime?) + * # ... + * end */ static VALUE rb_method_call_pass_called_kw(int argc, const VALUE *argv, VALUE method) { - VALUE procval = rb_block_given_p() ? rb_block_proc() : Qnil; - return rb_method_call_with_block_kw(argc, argv, method, procval, RB_PASS_CALLED_KEYWORDS); + return rb_method_call_kw(argc, argv, method, RB_PASS_CALLED_KEYWORDS); } VALUE @@ -2493,7 +2728,7 @@ rb_method_call_with_block_kw(int argc, const VALUE *argv, VALUE method, VALUE pa rb_execution_context_t *ec = GET_EC(); TypedData_Get_Struct(method, struct METHOD, &method_data_type, data); - if (data->recv == Qundef) { + if (UNDEF_P(data->recv)) { rb_raise(rb_eTypeError, "can't call unbound method; bind first"); } return call_method_data(ec, data, argc, argv, passed_procval, kw_splat); @@ -2509,7 +2744,7 @@ rb_method_call_with_block(int argc, const VALUE *argv, VALUE method, VALUE passe * * Document-class: UnboundMethod * - * Ruby supports two forms of objectified methods. Class Method is + * Ruby supports two forms of objectified methods. Class +Method+ is * used to represent methods that are associated with a particular * object: these method objects are bound to that object. Bound * method objects for an object can be created using Object#method. @@ -2563,7 +2798,7 @@ rb_method_call_with_block(int argc, const VALUE *argv, VALUE method, VALUE passe static void convert_umethod_to_method_components(const struct METHOD *data, VALUE recv, VALUE *methclass_out, VALUE *klass_out, VALUE *iclass_out, const rb_method_entry_t **me_out, const bool clone) { - VALUE methclass = data->me->owner; + VALUE methclass = data->owner; VALUE iclass = data->me->defined_class; VALUE klass = CLASS_OF(recv); @@ -2571,9 +2806,8 @@ convert_umethod_to_method_components(const struct METHOD *data, VALUE recv, VALU VALUE refined_class = rb_refinement_module_get_refined_class(methclass); if (!NIL_P(refined_class)) methclass = refined_class; } - if (!RB_TYPE_P(methclass, T_MODULE) && - methclass != CLASS_OF(recv) && !rb_obj_is_kind_of(recv, methclass)) { - if (FL_TEST(methclass, FL_SINGLETON)) { + if (!RB_TYPE_P(methclass, T_MODULE) && !RTEST(rb_obj_is_kind_of(recv, methclass))) { + if (RCLASS_SINGLETON_P(methclass)) { rb_raise(rb_eTypeError, "singleton method called for a different object"); } @@ -2594,7 +2828,7 @@ convert_umethod_to_method_components(const struct METHOD *data, VALUE recv, VALU if (RB_TYPE_P(me->owner, T_MODULE)) { if (!clone) { // if we didn't previously clone the method entry, then we need to clone it now - // because this branch manipualtes it in rb_method_entry_complement_defined_class + // because this branch manipulates it in rb_method_entry_complement_defined_class me = rb_method_entry_clone(me); } VALUE ic = rb_class_search_ancestor(klass, me->owner); @@ -2663,6 +2897,7 @@ umethod_bind(VALUE method, VALUE recv) RB_OBJ_WRITE(method, &bound->recv, recv); RB_OBJ_WRITE(method, &bound->klass, klass); RB_OBJ_WRITE(method, &bound->iclass, iclass); + RB_OBJ_WRITE(method, &bound->owner, methclass); RB_OBJ_WRITE(method, &bound->me, me); return method; @@ -2699,7 +2934,7 @@ umethod_bind_call(int argc, VALUE *argv, VALUE method) VALUE methclass, klass, iclass; const rb_method_entry_t *me; convert_umethod_to_method_components(data, recv, &methclass, &klass, &iclass, &me, false); - struct METHOD bound = { recv, klass, 0, me }; + struct METHOD bound = { recv, klass, 0, methclass, me }; return call_method_data(ec, &bound, argc, argv, passed_procval, RB_PASS_CALLED_KEYWORDS); } @@ -2851,7 +3086,11 @@ original_method_entry(VALUE mod, ID id) while ((me = rb_method_entry(mod, id)) != 0) { const rb_method_definition_t *def = me->def; - if (def->type != VM_METHOD_TYPE_ZSUPER) break; + + if (def->type != VM_METHOD_TYPE_ZSUPER && + (def->type != VM_METHOD_TYPE_CFUNC || + def->body.cfunc.func != (rb_cfunc_t)rb_zsuper_to_super)) break; + mod = RCLASS_SUPER(me->owner); id = def->original_id; } @@ -2970,14 +3209,6 @@ rb_method_entry_location(const rb_method_entry_t *me) return method_def_location(me->def); } -static const rb_method_definition_t * -zsuper_ref_method_def(VALUE method) -{ - const struct METHOD *data; - TypedData_Get_Struct(method, struct METHOD, &method_data_type, data); - return zsuper_resolve(data->me)->def; -} - /* * call-seq: * meth.source_location -> [String, Integer] @@ -2989,7 +3220,7 @@ zsuper_ref_method_def(VALUE method) VALUE rb_method_location(VALUE method) { - return method_def_location(zsuper_ref_method_def(method)); + return method_def_location(rb_method_def(method)); } static const rb_method_definition_t * @@ -3077,7 +3308,7 @@ method_def_parameters(const rb_method_definition_t *def) static VALUE rb_method_parameters(VALUE method) { - return method_def_parameters(zsuper_ref_method_def(method)); + return method_def_parameters(rb_method_def(method)); } /* @@ -3140,17 +3371,21 @@ method_inspect(VALUE method) defined_class = data->me->def->body.alias.original_me->owner; } else { - defined_class = method_entry_defined_class(zsuper_resolve(data->me)); + defined_class = method_entry_defined_class(data->me); } if (RB_TYPE_P(defined_class, T_ICLASS)) { defined_class = RBASIC_CLASS(defined_class); } - if (FL_TEST(mklass, FL_SINGLETON)) { - VALUE v = rb_ivar_get(mklass, attached); + if (UNDEF_P(data->recv)) { + // UnboundMethod + rb_str_buf_append(str, rb_inspect(defined_class)); + } + else if (RCLASS_SINGLETON_P(mklass)) { + VALUE v = RCLASS_ATTACHED_OBJECT(mklass); - if (data->recv == Qundef) { + if (UNDEF_P(data->recv)) { rb_str_buf_append(str, rb_inspect(mklass)); } else if (data->recv == v) { @@ -3167,8 +3402,8 @@ method_inspect(VALUE method) } else { mklass = data->klass; - if (FL_TEST(mklass, FL_SINGLETON)) { - VALUE v = rb_ivar_get(mklass, attached); + if (RCLASS_SINGLETON_P(mklass)) { + VALUE v = RCLASS_ATTACHED_OBJECT(mklass); if (!(RB_TYPE_P(v, T_CLASS) || RB_TYPE_P(v, T_MODULE))) { do { mklass = RCLASS_SUPER(mklass); @@ -3202,6 +3437,7 @@ method_inspect(VALUE method) const VALUE keyrest = ID2SYM(rb_intern("keyrest")); const VALUE block = ID2SYM(rb_intern("block")); const VALUE nokey = ID2SYM(rb_intern("nokey")); + const VALUE noblock = ID2SYM(rb_intern("noblock")); int forwarding = 0; rb_str_buf_cat2(str, "("); @@ -3219,9 +3455,10 @@ method_inspect(VALUE method) for (int i = 0; i < RARRAY_LEN(params); i++) { pair = RARRAY_AREF(params, i); kind = RARRAY_AREF(pair, 0); - name = RARRAY_AREF(pair, 1); - // FIXME: in tests it turns out that kind, name = [:req] produces name to be false. Why?.. - if (NIL_P(name) || name == Qfalse) { + if (RARRAY_LEN(pair) > 1) { + name = RARRAY_AREF(pair, 1); + } + else { // FIXME: can it be reduced to switch/case? if (kind == req || kind == opt) { name = rb_str_new2("_"); @@ -3235,6 +3472,12 @@ method_inspect(VALUE method) else if (kind == nokey) { name = rb_str_new2("nil"); } + else if (kind == noblock) { + name = rb_str_new2("nil"); + } + else { + name = Qnil; + } } if (kind == req) { @@ -3284,6 +3527,9 @@ method_inspect(VALUE method) else if (kind == nokey) { rb_str_buf_cat2(str, "**nil"); } + else if (kind == noblock) { + rb_str_buf_cat2(str, "&nil"); + } if (i < RARRAY_LEN(params) - 1) { rb_str_buf_cat2(str, ", "); @@ -3349,12 +3595,14 @@ method_to_proc(VALUE method) } extern VALUE rb_find_defined_class_by_owner(VALUE current_class, VALUE target_owner); +extern int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2); +rb_cref_t * rb_vm_get_cref(const VALUE *ep); /* * call-seq: * meth.super_method -> method * - * Returns a Method of superclass which would be called when super is used + * Returns a +Method+ of superclass which would be called when super is used * or nil if there is no method on superclass. */ @@ -3375,11 +3623,76 @@ method_super_method(VALUE method) mid = data->me->def->body.alias.original_me->def->original_id; } else { - super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass)); + VALUE klass = iclass; + if (RICLASS_FOR_REFINEMENT_P(klass)) { + // Refined methods need this check before superclass determination + klass = RBASIC(klass)->klass; + } + super_class = RCLASS_SUPER(RCLASS_ORIGIN(klass)); mid = data->me->def->original_id; } if (!super_class) return Qnil; - me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, mid, &iclass); + + // For refined methods, skip refinements for the same definition, but consider + // refinements for superclass methods + const rb_method_definition_t *skip_def = RICLASS_FOR_REFINEMENT_P(iclass) ? data->me->def : NULL; + + // Use the CREF of the Method/UnboundMethod, not the CREF of the caller of super_method. + // We must avoid the use of rb_callable_method_entry_with_refinements, as that will + // implicitly use the refinements activated in of the caller of super_method. + const rb_cref_t *cref = NULL; + switch (data->me->def->type) { + case VM_METHOD_TYPE_ISEQ: + cref = data->me->def->body.iseq.cref; + break; + case VM_METHOD_TYPE_BMETHOD: { + const rb_proc_t *proc; + GetProcPtr(data->me->def->body.bmethod.proc, proc); + const struct rb_block *block = &proc->block; + if (vm_block_type(block) == block_type_iseq) + cref = rb_vm_get_cref(block->as.captured.ep); + break; + } + default: + break; + } + VALUE klass = super_class; + me = NULL; + while (klass) { + const rb_callable_method_entry_t *cme = rb_callable_method_entry(klass, mid); + if (!cme) break; + if (cme->def->type != VM_METHOD_TYPE_REFINED) { + me = (rb_method_entry_t *)cme; + iclass = cme->defined_class; + break; + } + // Look through all CREF scopes for a refinement for cme->owner, mirroring + // the loop in search_refined_method. + const rb_cref_t *c; + for (c = cref; c; c = CREF_NEXT(c)) { + VALUE refs = CREF_REFINEMENTS(c); + if (NIL_P(refs)) continue; + VALUE r = rb_hash_lookup(refs, cme->owner); + if (NIL_P(r)) continue; + const rb_callable_method_entry_t *ref_cme = rb_callable_method_entry(r, mid); + if (!ref_cme) break; + if (ref_cme->def->type == VM_METHOD_TYPE_REFINED) continue; + if (skip_def && rb_method_definition_eq(ref_cme->def, skip_def)) continue; + me = (rb_method_entry_t *)ref_cme; + iclass = ref_cme->defined_class; + break; + } + if (me) break; + // No refined method found. Use orig_me if available, or normal method lookup + // in superclass otherwise. + const rb_method_entry_t *orig_me = cme->def->body.refined.orig_me; + if (orig_me) { + me = (rb_method_entry_t *)orig_me; + iclass = orig_me->defined_class ? orig_me->defined_class : cme->defined_class; + break; + } + klass = RCLASS_SUPER(cme->defined_class); + } if (!me) return Qnil; return mnew_internal(me, me->owner, iclass, data->recv, mid, rb_obj_class(method), FALSE, FALSE); } @@ -3427,9 +3740,15 @@ env_clone(const rb_env_t *env, const rb_cref_t *cref) } new_body = ALLOC_N(VALUE, env->env_size); - MEMCPY(new_body, env->env, VALUE, env->env_size); new_ep = &new_body[env->ep - env->env]; new_env = vm_env_new(new_ep, new_body, env->env_size, env->iseq); + + /* The memcpy has to happen after the vm_env_new because it can trigger a + * GC compaction which can move the objects in the env. */ + MEMCPY(new_body, env->env, VALUE, env->env_size); + /* VM_ENV_DATA_INDEX_ENV is set in vm_env_new but will get overwritten + * by the memcpy above. */ + new_ep[VM_ENV_DATA_INDEX_ENV] = (VALUE)new_env; RB_OBJ_WRITE(new_env, &new_ep[VM_ENV_DATA_INDEX_ME_CREF], (VALUE)cref); VM_ASSERT(VM_ENV_ESCAPED_P(new_ep)); return new_env; @@ -3486,7 +3805,7 @@ proc_binding(VALUE self) env = VM_ENV_ENVVAL_PTR(block->as.captured.ep); env = env_clone(env, method_cref(method)); /* set empty iseq */ - empty = rb_iseq_new(NULL, name, name, Qnil, 0, ISEQ_TYPE_TOP); + empty = rb_iseq_new(Qnil, name, name, Qnil, 0, ISEQ_TYPE_TOP); RB_OBJ_WRITE(env, &env->iseq, empty); break; } @@ -3507,7 +3826,7 @@ proc_binding(VALUE self) if (iseq) { rb_iseq_check(iseq); RB_OBJ_WRITE(bindval, &bind->pathobj, ISEQ_BODY(iseq)->location.pathobj); - bind->first_lineno = FIX2INT(rb_iseq_first_lineno(iseq)); + bind->first_lineno = ISEQ_BODY(iseq)->location.first_lineno; } else { RB_OBJ_WRITE(bindval, &bind->pathobj, @@ -3799,19 +4118,18 @@ rb_proc_compose_to_right(VALUE self, VALUE g) /* * call-seq: - * meth << g -> a_proc + * self << g -> a_proc * - * Returns a proc that is the composition of this method and the given <i>g</i>. - * The returned proc takes a variable number of arguments, calls <i>g</i> with them - * then calls this method with the result. + * Returns a proc that is the composition of the given +g+ and this method. * - * def f(x) - * x * x - * end + * The returned proc takes a variable number of arguments. It first calls +g+ + * with the arguments, then calls +self+ with the return value of +g+. + * + * def f(ary) = ary << 'in f' * * f = self.method(:f) - * g = proc {|x| x + x } - * p (f << g).call(2) #=> 16 + * g = proc { |ary| ary << 'in proc' } + * (f << g).call([]) # => ["in proc", "in f"] */ static VALUE rb_method_compose_to_left(VALUE self, VALUE g) @@ -3823,19 +4141,18 @@ rb_method_compose_to_left(VALUE self, VALUE g) /* * call-seq: - * meth >> g -> a_proc + * self >> g -> a_proc * - * Returns a proc that is the composition of this method and the given <i>g</i>. - * The returned proc takes a variable number of arguments, calls this method - * with them then calls <i>g</i> with the result. + * Returns a proc that is the composition of this method and the given +g+. * - * def f(x) - * x * x - * end + * The returned proc takes a variable number of arguments. It first calls +self+ + * with the arguments, then calls +g+ with the return value of +self+. + * + * def f(ary) = ary << 'in f' * * f = self.method(:f) - * g = proc {|x| x + x } - * p (f >> g).call(2) #=> 8 + * g = proc { |ary| ary << 'in proc' } + * (f >> g).call([]) # => ["in f", "in proc"] */ static VALUE rb_method_compose_to_right(VALUE self, VALUE g) @@ -3893,12 +4210,13 @@ proc_ruby2_keywords(VALUE procval) switch (proc->block.type) { case block_type_iseq: if (ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_rest && + !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_post && !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_kw && !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_kwrest) { ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.ruby2_keywords = 1; } else { - rb_warn("Skipping set of ruby2_keywords flag for proc (proc accepts keywords or proc does not accept argument splat)"); + rb_warn("Skipping set of ruby2_keywords flag for proc (proc accepts keywords or post arguments or proc does not accept argument splat)"); } break; default: @@ -4142,7 +4460,6 @@ proc_ruby2_keywords(VALUE procval) * a proc by the <code>&</code> operator, and therefore can be * consumed by iterators. * - * class Greeter * def initialize(greeting) * @greeting = greeting @@ -4158,8 +4475,8 @@ proc_ruby2_keywords(VALUE procval) * ["Bob", "Jane"].map(&hi) #=> ["Hi, Bob!", "Hi, Jane!"] * ["Bob", "Jane"].map(&hey) #=> ["Hey, Bob!", "Hey, Jane!"] * - * Of the Ruby core classes, this method is implemented by Symbol, - * Method, and Hash. + * Of the Ruby core classes, this method is implemented by +Symbol+, + * +Method+, and +Hash+. * * :to_s.to_proc.call(1) #=> "1" * [1, 2].map(&:to_s) #=> ["1", "2"] @@ -4193,19 +4510,86 @@ proc_ruby2_keywords(VALUE procval) * Since +return+ and +break+ exits the block itself in lambdas, * lambdas cannot be orphaned. * - * == Numbered parameters + * == Anonymous block parameters * - * Numbered parameters are implicitly defined block parameters intended to - * simplify writing short blocks: + * To simplify writing short blocks, Ruby provides two different types of + * anonymous parameters: +it+ (single parameter) and numbered ones: <tt>_1</tt>, + * <tt>_2</tt> and so on. * * # Explicit parameter: * %w[test me please].each { |str| puts str.upcase } # prints TEST, ME, PLEASE * (1..5).map { |i| i**2 } # => [1, 4, 9, 16, 25] * - * # Implicit parameter: + * # it: + * %w[test me please].each { puts it.upcase } # prints TEST, ME, PLEASE + * (1..5).map { it**2 } # => [1, 4, 9, 16, 25] + * + * # Numbered parameter: * %w[test me please].each { puts _1.upcase } # prints TEST, ME, PLEASE * (1..5).map { _1**2 } # => [1, 4, 9, 16, 25] * + * === +it+ + * + * +it+ is a name that is available inside a block when no explicit parameters + * defined, as shown above. + * + * %w[test me please].each { puts it.upcase } # prints TEST, ME, PLEASE + * (1..5).map { it**2 } # => [1, 4, 9, 16, 25] + * + * +it+ is a "soft keyword": it is not a reserved name, and can be used as + * a name for methods and local variables: + * + * it = 5 # no warnings + * def it(&block) # RSpec-like API, no warnings + * # ... + * end + * + * +it+ can be used as a local variable even in blocks that use it as an + * implicit parameter (though this style is obviously confusing): + * + * [1, 2, 3].each { + * # takes a value of implicit parameter "it" and uses it to + * # define a local variable with the same name + * it = it**2 + * p it + * } + * + * In a block with explicit parameters defined +it+ usage raises an exception: + * + * [1, 2, 3].each { |x| p it } + * # syntax error found (SyntaxError) + * # [1, 2, 3].each { |x| p it } + * # ^~ 'it' is not allowed when an ordinary parameter is defined + * + * But if a local name (variable or method) is available, it would be used: + * + * it = 5 + * [1, 2, 3].each { |x| p it } + * # Prints 5, 5, 5 + * + * Blocks using +it+ can be nested: + * + * %w[test me].each { it.each_char { p it } } + * # Prints "t", "e", "s", "t", "m", "e" + * + * Blocks using +it+ are considered to have one parameter: + * + * p = proc { it**2 } + * l = lambda { it**2 } + * p.parameters # => [[:opt]] + * p.arity # => 1 + * l.parameters # => [[:req]] + * l.arity # => 1 + * + * === Numbered parameters + * + * Numbered parameters are another way to name block parameters implicitly. + * Unlike +it+, numbered parameters allow to refer to several parameters + * in one block. + * + * %w[test me please].each { puts _1.upcase } # prints TEST, ME, PLEASE + * {a: 100, b: 200}.map { "#{_1} = #{_2}" } # => "a = 100", "b = 200" + * * Parameter names from +_1+ to +_9+ are supported: * * [10, 20, 30].zip([40, 50, 60], [70, 80, 90]).map { _1 + _2 + _3 } @@ -4220,11 +4604,16 @@ proc_ruby2_keywords(VALUE procval) * [10, 20, 30].map { |x| _1**2 } * # SyntaxError (ordinary parameter is defined) * + * Numbered parameters can't be mixed with +it+ either: + * + * [10, 20, 30].map { _1 + it } + * # SyntaxError: 'it' is not allowed when a numbered parameter is already used + * * To avoid conflicts, naming local variables or method - * arguments +_1+, +_2+ and so on, causes a warning. + * arguments +_1+, +_2+ and so on, causes an error. * - * _1 = 'test' - * # warning: `_1' is reserved as numbered parameter + * _1 = 'test' + * # ^~ _1 is reserved for numbered parameters (SyntaxError) * * Using implicit numbered parameters affects block's arity: * @@ -4238,14 +4627,12 @@ proc_ruby2_keywords(VALUE procval) * Blocks with numbered parameters can't be nested: * * %w[test me].each { _1.each_char { p _1 } } - * # SyntaxError (numbered parameter is already used in outer block here) + * # numbered parameter is already used in outer block (SyntaxError) * # %w[test me].each { _1.each_char { p _1 } } * # ^~ * - * Numbered parameters were introduced in Ruby 2.7. */ - void Init_Proc(void) { @@ -4270,7 +4657,7 @@ Init_Proc(void) rb_define_method(rb_cProc, "to_proc", proc_to_proc, 0); rb_define_method(rb_cProc, "arity", proc_arity, 0); rb_define_method(rb_cProc, "clone", proc_clone, 0); - rb_define_method(rb_cProc, "dup", rb_proc_dup, 0); + rb_define_method(rb_cProc, "dup", proc_dup, 0); rb_define_method(rb_cProc, "hash", proc_hash, 0); rb_define_method(rb_cProc, "to_s", proc_to_s, 0); rb_define_alias(rb_cProc, "inspect", "to_s"); @@ -4306,6 +4693,7 @@ Init_Proc(void) rb_define_method(rb_cMethod, "eql?", method_eq, 1); rb_define_method(rb_cMethod, "hash", method_hash, 0); rb_define_method(rb_cMethod, "clone", method_clone, 0); + rb_define_method(rb_cMethod, "dup", method_dup, 0); rb_define_method(rb_cMethod, "call", rb_method_call_pass_called_kw, -1); rb_define_method(rb_cMethod, "===", rb_method_call_pass_called_kw, -1); rb_define_method(rb_cMethod, "curry", rb_method_curry, -1); @@ -4328,14 +4716,17 @@ Init_Proc(void) rb_define_method(rb_mKernel, "public_method", rb_obj_public_method, 1); rb_define_method(rb_mKernel, "singleton_method", rb_obj_singleton_method, 1); + rb_define_method(rb_cMethod, "box", method_box, 0); + /* UnboundMethod */ rb_cUnboundMethod = rb_define_class("UnboundMethod", rb_cObject); rb_undef_alloc_func(rb_cUnboundMethod); rb_undef_method(CLASS_OF(rb_cUnboundMethod), "new"); - rb_define_method(rb_cUnboundMethod, "==", method_eq, 1); - rb_define_method(rb_cUnboundMethod, "eql?", method_eq, 1); + rb_define_method(rb_cUnboundMethod, "==", unbound_method_eq, 1); + rb_define_method(rb_cUnboundMethod, "eql?", unbound_method_eq, 1); rb_define_method(rb_cUnboundMethod, "hash", method_hash, 0); rb_define_method(rb_cUnboundMethod, "clone", method_clone, 0); + rb_define_method(rb_cUnboundMethod, "dup", method_dup, 0); rb_define_method(rb_cUnboundMethod, "arity", method_arity_m, 0); rb_define_method(rb_cUnboundMethod, "inspect", method_inspect, 0); rb_define_method(rb_cUnboundMethod, "to_s", method_inspect, 0); @@ -4398,6 +4789,8 @@ Init_Proc(void) void Init_Binding(void) { + rb_gc_register_address(&sym_proc_cache); + rb_cBinding = rb_define_class("Binding", rb_cObject); rb_undef_alloc_func(rb_cBinding); rb_undef_method(CLASS_OF(rb_cBinding), "new"); @@ -4408,6 +4801,9 @@ Init_Binding(void) rb_define_method(rb_cBinding, "local_variable_get", bind_local_variable_get, 1); rb_define_method(rb_cBinding, "local_variable_set", bind_local_variable_set, 2); rb_define_method(rb_cBinding, "local_variable_defined?", bind_local_variable_defined_p, 1); + rb_define_method(rb_cBinding, "implicit_parameters", bind_implicit_parameters, 0); + rb_define_method(rb_cBinding, "implicit_parameter_get", bind_implicit_parameter_get, 1); + rb_define_method(rb_cBinding, "implicit_parameter_defined?", bind_implicit_parameter_defined_p, 1); rb_define_method(rb_cBinding, "receiver", bind_receiver, 0); rb_define_method(rb_cBinding, "source_location", bind_location, 0); rb_define_global_function("binding", rb_f_binding, 0); |
