diff --git a/vm_args.c b/vm_args.c index fe7b7f0ca1..240835083a 100644 --- a/vm_args.c +++ b/vm_args.c @@ -187,6 +187,75 @@ args_rest_array(struct args_info *args) return ary; } +static int +keyword_hash_has_symbol_p_iter(st_data_t key, st_data_t val, st_data_t arg) +{ + if (SYMBOL_P((VALUE)key)) { + *(int*)arg |= 1; + return ST_STOP; + } + + return ST_CONTINUE; +} + +static int +keyword_hash_has_symbol_p(VALUE kw_hash) { + int has_symbol = 0; + rb_hash_stlike_foreach(kw_hash, keyword_hash_has_symbol_p_iter, (st_data_t)(&has_symbol)); + return has_symbol; +} + +static int +keyword_hash_p(VALUE *kw_hash_ptr, int check_symbol) +{ + *kw_hash_ptr = rb_check_hash_type(*kw_hash_ptr); + + if (NIL_P(*kw_hash_ptr)) { + return FALSE; + } + + if (!check_symbol || RHASH_EMPTY_P(*kw_hash_ptr) || + keyword_hash_has_symbol_p(*kw_hash_ptr)) { + return TRUE; + } + + *kw_hash_ptr = Qnil; + return FALSE; +} + +static VALUE +args_pop_keyword_hash(struct args_info *args, VALUE *kw_hash_ptr, int check_symbol) +{ + if (args->rest == Qfalse) { + from_argv: + VM_ASSERT(args->argc > 0); + *kw_hash_ptr = args->argv[args->argc-1]; + + if (keyword_hash_p(kw_hash_ptr, check_symbol)) { + args->argc--; + return TRUE; + } + } + else { + long len = RARRAY_LEN(args->rest); + + if (len > 0) { + *kw_hash_ptr = RARRAY_AREF(args->rest, len - 1); + + if (keyword_hash_p(kw_hash_ptr, check_symbol)) { + arg_rest_dup(args); + rb_ary_pop(args->rest); + return TRUE; + } + } + else { + goto from_argv; + } + } + + return FALSE; +} + static int args_kw_argv_to_hash(struct args_info *args) { @@ -452,6 +521,82 @@ ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq, unsigned RHASH_EMPTY_P(keyword_hash); } +VALUE rb_iseq_location(const rb_iseq_t *iseq); + +static st_table *caller_to_callees = 0; + +static VALUE +rb_warn_check(const rb_execution_context_t * const ec, const rb_iseq_t *const iseq) +{ + if (!rb_warning_category_enabled_p(RB_WARN_CATEGORY_DEPRECATED)) return 1; + + if (!iseq) return 0; + + const st_data_t callee = (st_data_t)(iseq->body->iseq_unique_id * 2); + + const rb_control_frame_t * const cfp = rb_vm_get_ruby_level_next_cfp(ec, ec->cfp); + + if (!cfp) return 0; + + const st_data_t caller = (st_data_t)cfp->pc; + + if (!caller_to_callees) { + caller_to_callees = st_init_numtable(); + } + + st_data_t val; + if (st_lookup(caller_to_callees, caller, &val)) { + st_table *callees; + + if (val & 1) { + val &= ~(st_data_t)1; + if (val == callee) return 1; /* already warned */ + + callees = st_init_numtable(); + st_insert(callees, val, 1); + } + else { + callees = (st_table *) val; + if (st_is_member(callees, callee)) return 1; /* already warned */ + } + st_insert(callees, callee, 1); + st_insert(caller_to_callees, caller, (st_data_t) callees); + } + else { + st_insert(caller_to_callees, caller, callee | 1); + } + + return 0; /* not warned yet for the pair of caller and callee */ +} + +static inline void +rb_warn_last_hash_to_keyword(rb_execution_context_t * const ec, + struct rb_calling_info * const calling, + const struct rb_callinfo *ci, + const rb_iseq_t * const iseq) +{ + if (rb_warn_check(ec, iseq)) return; + + VALUE name, loc; + name = rb_id2str(vm_ci_mid(ci)); + loc = rb_iseq_location(iseq); + if (NIL_P(loc)) { + rb_warning("Using the last argument for `%"PRIsVALUE"' as keyword parameters is deprecated; maybe ** should be added to the call", + name); + } + else { + rb_warning("Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call"); + if (calling->recv != Qundef) { + rb_compile_warning(RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1)), + "The called method `%"PRIsVALUE"' is defined here", name); + } + else { + rb_compile_warning(RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1)), + "The called method is defined here"); + } + } +} + static int setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * const iseq, struct rb_calling_info *const calling, @@ -626,6 +771,14 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co } } + if (!kw_flag && args->kw_argv == NULL && + (iseq->body->param.flags.has_kw || iseq->body->param.flags.has_kwrest) && + given_argc > min_argc && + args_pop_keyword_hash(args, &keyword_hash, !iseq->body->param.flags.has_kwrest)) { + rb_warn_last_hash_to_keyword(ec, calling, ci, iseq); + given_argc--; + } + if (given_argc > max_argc && max_argc != UNLIMITED_ARGUMENTS) { if (arg_setup_type == arg_setup_block) { /* truncate */