1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Fully separate positional arguments and keyword arguments

This removes the warnings added in 2.7, and changes the behavior
so that a final positional hash is not treated as keywords or
vice-versa.

To handle the arg_setup_block splat case correctly with keyword
arguments, we need to check if we are taking a keyword hash.
That case didn't have a test, but it affects real-world code,
so add a test for it.

This removes rb_empty_keyword_given_p() and related code, as
that is not needed in Ruby 3.  The empty keyword case is the
same as the no keyword case in Ruby 3.

This changes rb_scan_args to implement keyword argument
separation for C functions when the : character is used.
For backwards compatibility, it returns a duped hash.
This is a bad idea for performance, but not duping the hash
breaks at least Enumerator::ArithmeticSequence#inspect.

Instead of having RB_PASS_CALLED_KEYWORDS be a number,
simplify the code by just making it be rb_keyword_given_p().
This commit is contained in:
Jeremy Evans 2019-10-04 12:51:57 -07:00
parent 8ba261c754
commit beae6cbf0f
Notes: git 2020-01-03 11:41:10 +09:00
16 changed files with 96 additions and 679 deletions

70
class.c
View file

@ -1970,7 +1970,6 @@ rb_scan_args_parse(int kw_flag, int argc, const VALUE *argv, const char *fmt, st
const char *p = fmt; const char *p = fmt;
VALUE *tmp_buffer = arg->tmp_buffer; VALUE *tmp_buffer = arg->tmp_buffer;
int keyword_given = 0; int keyword_given = 0;
int empty_keyword_given = 0;
int last_hash_keyword = 0; int last_hash_keyword = 0;
memset(arg, 0, sizeof(*arg)); memset(arg, 0, sizeof(*arg));
@ -1979,16 +1978,11 @@ rb_scan_args_parse(int kw_flag, int argc, const VALUE *argv, const char *fmt, st
switch (kw_flag) { switch (kw_flag) {
case RB_SCAN_ARGS_PASS_CALLED_KEYWORDS: case RB_SCAN_ARGS_PASS_CALLED_KEYWORDS:
if (!(keyword_given = rb_keyword_given_p())) { keyword_given = rb_keyword_given_p();
empty_keyword_given = rb_empty_keyword_given_p();
}
break; break;
case RB_SCAN_ARGS_KEYWORDS: case RB_SCAN_ARGS_KEYWORDS:
keyword_given = 1; keyword_given = 1;
break; break;
case RB_SCAN_ARGS_EMPTY_KEYWORDS:
empty_keyword_given = 1;
break;
case RB_SCAN_ARGS_LAST_HASH_KEYWORDS: case RB_SCAN_ARGS_LAST_HASH_KEYWORDS:
last_hash_keyword = 1; last_hash_keyword = 1;
break; break;
@ -2023,67 +2017,13 @@ rb_scan_args_parse(int kw_flag, int argc, const VALUE *argv, const char *fmt, st
} }
arg->n_mand = arg->n_lead + arg->n_trail; arg->n_mand = arg->n_lead + arg->n_trail;
/* capture an option hash - phase 1: pop */ if (arg->f_hash && argc > 0) {
/* Ignore final positional hash if empty keywords given */
if (argc > 0 && !(arg->f_hash && empty_keyword_given)) {
VALUE last = argv[argc - 1]; VALUE last = argv[argc - 1];
if (arg->f_hash && arg->n_mand < argc) { if (keyword_given || (last_hash_keyword && RB_TYPE_P(last, T_HASH))) {
if (keyword_given) { arg->hash = rb_hash_dup(last);
if (!RB_TYPE_P(last, T_HASH)) { argc--;
rb_warn("Keyword flag set when calling rb_scan_args, but last entry is not a hash");
}
else {
arg->hash = last;
}
}
else if (NIL_P(last)) {
/* For backwards compatibility, nil is taken as an empty
option hash only if it is not ambiguous; i.e. '*' is
not specified and arguments are given more than sufficient.
This will be removed in Ruby 3. */
if (!arg->f_var && arg->n_mand + arg->n_opt < argc) {
rb_warn("The last argument is nil, treating as empty keywords");
argc--;
}
}
else {
arg->hash = rb_check_hash_type(last);
}
/* Ruby 3: Remove if branch, as it will not attempt to split hashes */
if (!NIL_P(arg->hash)) {
VALUE opts = rb_extract_keywords(&arg->hash);
if (!(arg->last_hash = arg->hash)) {
if (!keyword_given && !last_hash_keyword) {
/* Warn if treating positional as keyword, as in Ruby 3,
this will be an error */
rb_warn("Using the last argument as keyword parameters is deprecated");
}
argc--;
}
else {
/* Warn if splitting either positional hash to keywords or keywords
to positional hash, as in Ruby 3, no splitting will be done */
rb_warn("The last argument is split into positional and keyword parameters");
arg->last_idx = argc - 1;
}
arg->hash = opts ? opts : Qnil;
}
} }
else if (arg->f_hash && keyword_given && arg->n_mand == argc) {
/* Warn if treating keywords as positional, as in Ruby 3, this will be an error */
rb_warn("Passing the keyword argument as the last hash parameter is deprecated");
}
}
if (arg->f_hash && arg->n_mand == argc+1 && empty_keyword_given) {
VALUE *ptr = rb_alloc_tmp_buffer2(tmp_buffer, argc+1, sizeof(VALUE));
memcpy(ptr, argv, sizeof(VALUE)*argc);
ptr[argc] = rb_hash_new();
argc++;
*(&argv) = ptr;
rb_warn("Passing the keyword argument as the last hash parameter is deprecated");
} }
arg->argc = argc; arg->argc = argc;

12
cont.c
View file

@ -1790,8 +1790,6 @@ rb_fiber_new(rb_block_call_func_t func, VALUE obj)
static void rb_fiber_terminate(rb_fiber_t *fiber, int need_interrupt); static void rb_fiber_terminate(rb_fiber_t *fiber, int need_interrupt);
#define PASS_KW_SPLAT (rb_empty_keyword_given_p() ? RB_PASS_EMPTY_KEYWORDS : rb_keyword_given_p())
void void
rb_fiber_start(void) rb_fiber_start(void)
{ {
@ -1809,7 +1807,6 @@ rb_fiber_start(void)
rb_context_t *cont = &VAR_FROM_MEMORY(fiber)->cont; rb_context_t *cont = &VAR_FROM_MEMORY(fiber)->cont;
int argc; int argc;
const VALUE *argv, args = cont->value; const VALUE *argv, args = cont->value;
int kw_splat = cont->kw_splat;
GetProcPtr(fiber->first_proc, proc); GetProcPtr(fiber->first_proc, proc);
argv = (argc = cont->argc) > 1 ? RARRAY_CONST_PTR(args) : &args; argv = (argc = cont->argc) > 1 ? RARRAY_CONST_PTR(args) : &args;
cont->value = Qnil; cont->value = Qnil;
@ -1818,8 +1815,7 @@ rb_fiber_start(void)
th->ec->root_svar = Qfalse; th->ec->root_svar = Qfalse;
EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_FIBER_SWITCH, th->self, 0, 0, 0, Qnil); EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_FIBER_SWITCH, th->self, 0, 0, 0, Qnil);
rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); cont->value = rb_vm_invoke_proc(th->ec, proc, argc, argv, cont->kw_splat, VM_BLOCK_HANDLER_NONE);
cont->value = rb_vm_invoke_proc(th->ec, proc, argc, argv, kw_splat, VM_BLOCK_HANDLER_NONE);
} }
EC_POP_TAG(); EC_POP_TAG();
@ -2163,7 +2159,7 @@ rb_fiber_alive_p(VALUE fiber_value)
static VALUE static VALUE
rb_fiber_m_resume(int argc, VALUE *argv, VALUE fiber) rb_fiber_m_resume(int argc, VALUE *argv, VALUE fiber)
{ {
return rb_fiber_resume_kw(fiber, argc, argv, PASS_KW_SPLAT); return rb_fiber_resume_kw(fiber, argc, argv, rb_keyword_given_p());
} }
/* /*
@ -2249,7 +2245,7 @@ rb_fiber_m_transfer(int argc, VALUE *argv, VALUE fiber_value)
{ {
rb_fiber_t *fiber = fiber_ptr(fiber_value); rb_fiber_t *fiber = fiber_ptr(fiber_value);
fiber->transferred = 1; fiber->transferred = 1;
return fiber_switch(fiber, argc, argv, 0, PASS_KW_SPLAT); return fiber_switch(fiber, argc, argv, 0, rb_keyword_given_p());
} }
/* /*
@ -2265,7 +2261,7 @@ rb_fiber_m_transfer(int argc, VALUE *argv, VALUE fiber_value)
static VALUE static VALUE
rb_fiber_s_yield(int argc, VALUE *argv, VALUE klass) rb_fiber_s_yield(int argc, VALUE *argv, VALUE klass)
{ {
return rb_fiber_yield_kw(argc, argv, PASS_KW_SPLAT); return rb_fiber_yield_kw(argc, argv, rb_keyword_given_p());
} }
/* /*

View file

@ -384,8 +384,6 @@ enumerator_allocate(VALUE klass)
return enum_obj; return enum_obj;
} }
#define PASS_KW_SPLAT (rb_empty_keyword_given_p() ? RB_PASS_EMPTY_KEYWORDS : rb_keyword_given_p())
static VALUE static VALUE
enumerator_init(VALUE enum_obj, VALUE obj, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, VALUE size, int kw_splat) enumerator_init(VALUE enum_obj, VALUE obj, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, VALUE size, int kw_splat)
{ {
@ -480,7 +478,7 @@ enumerator_initialize(int argc, VALUE *argv, VALUE obj)
meth = *argv++; meth = *argv++;
--argc; --argc;
} }
kw_splat = PASS_KW_SPLAT; kw_splat = rb_keyword_given_p();
} }
return enumerator_init(obj, recv, meth, argc, argv, 0, size, kw_splat); return enumerator_init(obj, recv, meth, argc, argv, 0, size, kw_splat);
@ -535,10 +533,10 @@ rb_enumeratorize_with_size(VALUE obj, VALUE meth, int argc, const VALUE *argv, r
/* Similar effect as calling obj.to_enum, i.e. dispatching to either /* Similar effect as calling obj.to_enum, i.e. dispatching to either
Kernel#to_enum vs Lazy#to_enum */ Kernel#to_enum vs Lazy#to_enum */
if (RTEST(rb_obj_is_kind_of(obj, rb_cLazy))) if (RTEST(rb_obj_is_kind_of(obj, rb_cLazy)))
return lazy_to_enum_i(obj, meth, argc, argv, size_fn, PASS_KW_SPLAT); return lazy_to_enum_i(obj, meth, argc, argv, size_fn, rb_keyword_given_p());
else else
return enumerator_init(enumerator_allocate(rb_cEnumerator), return enumerator_init(enumerator_allocate(rb_cEnumerator),
obj, meth, argc, argv, size_fn, Qnil, PASS_KW_SPLAT); obj, meth, argc, argv, size_fn, Qnil, rb_keyword_given_p());
} }
VALUE VALUE
@ -1892,7 +1890,7 @@ lazy_add_method(VALUE obj, int argc, VALUE *argv, VALUE args, VALUE memo,
static VALUE static VALUE
enumerable_lazy(VALUE obj) enumerable_lazy(VALUE obj)
{ {
VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size, PASS_KW_SPLAT); VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size, rb_keyword_given_p());
/* Qfalse indicates that the Enumerator::Lazy has no method name */ /* Qfalse indicates that the Enumerator::Lazy has no method name */
rb_ivar_set(result, id_method, Qfalse); rb_ivar_set(result, id_method, Qfalse);
return result; return result;
@ -1940,7 +1938,7 @@ lazy_to_enum(int argc, VALUE *argv, VALUE self)
if (RTEST((super_meth = rb_hash_aref(lazy_use_super_method, meth)))) { if (RTEST((super_meth = rb_hash_aref(lazy_use_super_method, meth)))) {
meth = super_meth; meth = super_meth;
} }
lazy = lazy_to_enum_i(self, meth, argc, argv, 0, PASS_KW_SPLAT); lazy = lazy_to_enum_i(self, meth, argc, argv, 0, rb_keyword_given_p());
if (rb_block_given_p()) { if (rb_block_given_p()) {
enumerator_ptr(lazy)->size = rb_block_proc(); enumerator_ptr(lazy)->size = rb_block_proc();
} }
@ -3318,7 +3316,7 @@ rb_arith_seq_new(VALUE obj, VALUE meth, int argc, VALUE const *argv,
VALUE beg, VALUE end, VALUE step, int excl) VALUE beg, VALUE end, VALUE step, int excl)
{ {
VALUE aseq = enumerator_init(enumerator_allocate(rb_cArithSeq), VALUE aseq = enumerator_init(enumerator_allocate(rb_cArithSeq),
obj, meth, argc, argv, size_fn, Qnil, PASS_KW_SPLAT); obj, meth, argc, argv, size_fn, Qnil, rb_keyword_given_p());
rb_ivar_set(aseq, id_begin, beg); rb_ivar_set(aseq, id_begin, beg);
rb_ivar_set(aseq, id_end, end); rb_ivar_set(aseq, id_end, end);
rb_ivar_set(aseq, id_step, step); rb_ivar_set(aseq, id_step, step);

8
eval.c
View file

@ -924,14 +924,6 @@ rb_keyword_given_p(void)
return rb_vm_cframe_keyword_p(GET_EC()->cfp); return rb_vm_cframe_keyword_p(GET_EC()->cfp);
} }
/* -- Remove In 3.0 -- */
int rb_vm_cframe_empty_keyword_p(const rb_control_frame_t *cfp);
int
rb_empty_keyword_given_p(void)
{
return rb_vm_cframe_empty_keyword_p(GET_EC()->cfp);
}
VALUE rb_eThreadError; VALUE rb_eThreadError;
/*! Declares that the current method needs a block. /*! Declares that the current method needs a block.

View file

@ -259,15 +259,6 @@ scan_args_k_lead_opt_hash(int argc, VALUE *argv, VALUE self)
return rb_ary_new_from_values(numberof(args), args); return rb_ary_new_from_values(numberof(args), args);
} }
static VALUE
scan_args_e_lead_opt_hash(int argc, VALUE *argv, VALUE self)
{
VALUE args[4];
int n = rb_scan_args_kw(RB_SCAN_ARGS_EMPTY_KEYWORDS, argc, argv, "11:", args+1, args+2, args+3);
args[0] = INT2NUM(n);
return rb_ary_new_from_values(numberof(args), args);
}
static VALUE static VALUE
scan_args_n_lead_opt_hash(int argc, VALUE *argv, VALUE self) scan_args_n_lead_opt_hash(int argc, VALUE *argv, VALUE self)
{ {
@ -310,6 +301,5 @@ Init_scan_args(void)
rb_define_singleton_method(module, "opt_var_trail_hash", scan_args_opt_var_trail_hash, -1); rb_define_singleton_method(module, "opt_var_trail_hash", scan_args_opt_var_trail_hash, -1);
rb_define_singleton_method(module, "lead_opt_var_trail_hash", scan_args_lead_opt_var_trail_hash, -1); rb_define_singleton_method(module, "lead_opt_var_trail_hash", scan_args_lead_opt_var_trail_hash, -1);
rb_define_singleton_method(module, "k_lead_opt_hash", scan_args_k_lead_opt_hash, -1); rb_define_singleton_method(module, "k_lead_opt_hash", scan_args_k_lead_opt_hash, -1);
rb_define_singleton_method(module, "e_lead_opt_hash", scan_args_e_lead_opt_hash, -1);
rb_define_singleton_method(module, "n_lead_opt_hash", scan_args_n_lead_opt_hash, -1); rb_define_singleton_method(module, "n_lead_opt_hash", scan_args_n_lead_opt_hash, -1);
} }

View file

@ -1901,7 +1901,6 @@ VALUE rb_funcall_with_block_kw(VALUE, ID, int, const VALUE*, VALUE, int);
int rb_scan_args(int, const VALUE*, const char*, ...); int rb_scan_args(int, const VALUE*, const char*, ...);
#define RB_SCAN_ARGS_PASS_CALLED_KEYWORDS 0 #define RB_SCAN_ARGS_PASS_CALLED_KEYWORDS 0
#define RB_SCAN_ARGS_KEYWORDS 1 #define RB_SCAN_ARGS_KEYWORDS 1
#define RB_SCAN_ARGS_EMPTY_KEYWORDS 2 /* Will be removed in 3.0 */
#define RB_SCAN_ARGS_LAST_HASH_KEYWORDS 3 #define RB_SCAN_ARGS_LAST_HASH_KEYWORDS 3
int rb_scan_args_kw(int, int, const VALUE*, const char*, ...); int rb_scan_args_kw(int, int, const VALUE*, const char*, ...);
VALUE rb_call_super(int, const VALUE*); VALUE rb_call_super(int, const VALUE*);
@ -1976,8 +1975,7 @@ VALUE rb_yield_splat_kw(VALUE, int);
VALUE rb_yield_block(RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, callback_arg)); /* rb_block_call_func */ VALUE rb_yield_block(RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, callback_arg)); /* rb_block_call_func */
#define RB_NO_KEYWORDS 0 #define RB_NO_KEYWORDS 0
#define RB_PASS_KEYWORDS 1 #define RB_PASS_KEYWORDS 1
#define RB_PASS_EMPTY_KEYWORDS 2 /* Will be removed in 3.0 */ #define RB_PASS_CALLED_KEYWORDS rb_keyword_given_p()
#define RB_PASS_CALLED_KEYWORDS 3
int rb_keyword_given_p(void); int rb_keyword_given_p(void);
int rb_block_given_p(void); int rb_block_given_p(void);
void rb_need_block(void); void rb_need_block(void);
@ -2331,9 +2329,6 @@ unsigned long ruby_strtoul(const char *str, char **endptr, int base);
PRINTF_ARGS(int ruby_snprintf(char *str, size_t n, char const *fmt, ...), 3, 4); PRINTF_ARGS(int ruby_snprintf(char *str, size_t n, char const *fmt, ...), 3, 4);
int ruby_vsnprintf(char *str, size_t n, char const *fmt, va_list ap); int ruby_vsnprintf(char *str, size_t n, char const *fmt, va_list ap);
/* -- Remove In 3.0, Only public for rb_scan_args optimized version -- */
int rb_empty_keyword_given_p(void);
#if defined(HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P) && defined(HAVE_VA_ARGS_MACRO) && defined(__OPTIMIZE__) #if defined(HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P) && defined(HAVE_VA_ARGS_MACRO) && defined(__OPTIMIZE__)
# define rb_scan_args(argc,argvp,fmt,...) \ # define rb_scan_args(argc,argvp,fmt,...) \
__builtin_choose_expr(__builtin_constant_p(fmt), \ __builtin_choose_expr(__builtin_constant_p(fmt), \
@ -2525,78 +2520,13 @@ rb_scan_args_set(int argc, const VALUE *argv,
int i, argi = 0, vari = 0, last_idx = -1; int i, argi = 0, vari = 0, last_idx = -1;
VALUE *var, hash = Qnil, last_hash = 0; VALUE *var, hash = Qnil, last_hash = 0;
const int n_mand = n_lead + n_trail; const int n_mand = n_lead + n_trail;
int keyword_given = rb_keyword_given_p();
int empty_keyword_given = 0;
VALUE tmp_buffer = 0; VALUE tmp_buffer = 0;
if (!keyword_given) { if (f_hash && argc > 0 && rb_keyword_given_p()) {
empty_keyword_given = rb_empty_keyword_given_p(); hash = rb_hash_dup(argv[argc - 1]);
argc--;
} }
/* capture an option hash - phase 1: pop */
/* Ignore final positional hash if empty keywords given */
if (argc > 0 && !(f_hash && empty_keyword_given)) {
VALUE last = argv[argc - 1];
if (f_hash && n_mand < argc) {
if (keyword_given) {
if (!RB_TYPE_P(last, T_HASH)) {
rb_warn("Keyword flag set when calling rb_scan_args, but last entry is not a hash");
}
else {
hash = last;
}
}
else if (NIL_P(last)) {
/* For backwards compatibility, nil is taken as an empty
option hash only if it is not ambiguous; i.e. '*' is
not specified and arguments are given more than sufficient.
This will be removed in Ruby 3. */
if (!f_var && n_mand + n_opt < argc) {
rb_warn("The last argument is nil, treating as empty keywords");
argc--;
}
}
else {
hash = rb_check_hash_type(last);
}
/* Ruby 3: Remove if branch, as it will not attempt to split hashes */
if (!NIL_P(hash)) {
VALUE opts = rb_extract_keywords(&hash);
if (!(last_hash = hash)) {
if (!keyword_given) {
/* Warn if treating positional as keyword, as in Ruby 3,
this will be an error */
rb_warn("Using the last argument as keyword parameters is deprecated");
}
argc--;
}
else {
/* Warn if splitting either positional hash to keywords or keywords
to positional hash, as in Ruby 3, no splitting will be done */
rb_warn("The last argument is split into positional and keyword parameters");
last_idx = argc - 1;
}
hash = opts ? opts : Qnil;
}
}
else if (f_hash && keyword_given && n_mand == argc) {
/* Warn if treating keywords as positional, as in Ruby 3, this will be an error */
rb_warn("Passing the keyword argument as the last hash parameter is deprecated");
}
}
if (f_hash && n_mand > 0 && n_mand == argc+1 && empty_keyword_given) {
VALUE *ptr = (VALUE *)rb_alloc_tmp_buffer2(&tmp_buffer, argc+1, sizeof(VALUE));
memcpy(ptr, argv, sizeof(VALUE)*argc);
ptr[argc] = rb_hash_new();
argc++;
*(&argv) = ptr;
rb_warn("Passing the keyword argument as the last hash parameter is deprecated");
}
if (argc < n_mand) { if (argc < n_mand) {
goto argc_error; goto argc_error;
} }

View file

@ -138,7 +138,6 @@ VALUE rb_lambda_call(VALUE obj, ID mid, int argc, const VALUE *argv,
MJIT_SYMBOL_EXPORT_BEGIN MJIT_SYMBOL_EXPORT_BEGIN
VALUE rb_vm_call0(struct rb_execution_context_struct *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callable_method_entry_struct *me, int kw_splat); VALUE rb_vm_call0(struct rb_execution_context_struct *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callable_method_entry_struct *me, int kw_splat);
VALUE rb_adjust_argv_kw_splat(int *argc, const VALUE **argv, int *kw_splat);
VALUE rb_vm_call_kw(struct rb_execution_context_struct *ec, VALUE recv, VALUE id, int argc, const VALUE *argv, const struct rb_callable_method_entry_struct *me, int kw_splat); VALUE rb_vm_call_kw(struct rb_execution_context_struct *ec, VALUE recv, VALUE id, int argc, const VALUE *argv, const struct rb_callable_method_entry_struct *me, int kw_splat);
VALUE rb_make_no_method_exception(VALUE exc, VALUE format, VALUE obj, int argc, const VALUE *argv, int priv); VALUE rb_make_no_method_exception(VALUE exc, VALUE format, VALUE obj, int argc, const VALUE *argv, int priv);
MJIT_SYMBOL_EXPORT_END MJIT_SYMBOL_EXPORT_END

View file

@ -4086,9 +4086,7 @@ rb_obj_dig(int argc, VALUE *argv, VALUE obj, VALUE notfound)
} }
return rb_check_funcall_with_hook_kw(obj, id_dig, argc, argv, return rb_check_funcall_with_hook_kw(obj, id_dig, argc, argv,
no_dig_method, obj, no_dig_method, obj,
rb_empty_keyword_given_p() ? RB_NO_KEYWORDS);
RB_PASS_EMPTY_KEYWORDS :
RB_NO_KEYWORDS);
} }
return obj; return obj;
} }

5
proc.c
View file

@ -955,14 +955,11 @@ rb_proc_call_kw(VALUE self, VALUE args, int kw_splat)
{ {
VALUE vret; VALUE vret;
rb_proc_t *proc; rb_proc_t *proc;
VALUE v;
int argc = check_argc(RARRAY_LEN(args)); int argc = check_argc(RARRAY_LEN(args));
const VALUE *argv = RARRAY_CONST_PTR(args); const VALUE *argv = RARRAY_CONST_PTR(args);
GetProcPtr(self, proc); GetProcPtr(self, proc);
v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat);
vret = rb_vm_invoke_proc(GET_EC(), proc, argc, argv, vret = rb_vm_invoke_proc(GET_EC(), proc, argc, argv,
kw_splat, VM_BLOCK_HANDLER_NONE); kw_splat, VM_BLOCK_HANDLER_NONE);
rb_free_tmp_buffer(&v);
RB_GC_GUARD(self); RB_GC_GUARD(self);
RB_GC_GUARD(args); RB_GC_GUARD(args);
return vret; return vret;
@ -994,10 +991,8 @@ rb_proc_call_with_block_kw(VALUE self, int argc, const VALUE *argv, VALUE passed
rb_execution_context_t *ec = GET_EC(); rb_execution_context_t *ec = GET_EC();
VALUE vret; VALUE vret;
rb_proc_t *proc; rb_proc_t *proc;
VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat);
GetProcPtr(self, proc); GetProcPtr(self, proc);
vret = rb_vm_invoke_proc(ec, proc, argc, argv, kw_splat, proc_to_block_handler(passed_procval)); vret = rb_vm_invoke_proc(ec, proc, argc, argv, kw_splat, proc_to_block_handler(passed_procval));
rb_free_tmp_buffer(&v);
RB_GC_GUARD(self); RB_GC_GUARD(self);
return vret; return vret;
} }

View file

@ -1087,6 +1087,16 @@ class TestProc < Test::Unit::TestCase
assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]") assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]")
end end
def test_proc_single_arg_with_keywords_accepted_and_yielded
def self.a
yield [], **{a: 1}
end
res = a do |arg, **opts|
[arg, opts]
end
assert_equal([[], {a: 1}], res)
end
def test_parameters def test_parameters
assert_equal([], proc {}.parameters) assert_equal([], proc {}.parameters)
assert_equal([], proc {||}.parameters) assert_equal([], proc {||}.parameters)

View file

@ -681,7 +681,6 @@ thread_do_start(rb_thread_t *th)
if (th->invoke_type == thread_invoke_type_proc) { if (th->invoke_type == thread_invoke_type_proc) {
VALUE args = th->invoke_arg.proc.args; VALUE args = th->invoke_arg.proc.args;
int args_len = (int)RARRAY_LEN(args); int args_len = (int)RARRAY_LEN(args);
int kw_splat = th->invoke_arg.proc.kw_splat;
const VALUE *args_ptr; const VALUE *args_ptr;
VALUE procval = th->invoke_arg.proc.proc; VALUE procval = th->invoke_arg.proc.proc;
rb_proc_t *proc; rb_proc_t *proc;
@ -704,10 +703,11 @@ thread_do_start(rb_thread_t *th)
args_ptr = RARRAY_CONST_PTR(args); args_ptr = RARRAY_CONST_PTR(args);
} }
rb_adjust_argv_kw_splat(&args_len, &args_ptr, &kw_splat); vm_check_ints_blocking(th->ec);
th->value = rb_vm_invoke_proc(th->ec, proc, th->value = rb_vm_invoke_proc(th->ec, proc,
args_len, args_ptr, args_len, args_ptr,
kw_splat, VM_BLOCK_HANDLER_NONE); th->invoke_arg.proc.kw_splat,
VM_BLOCK_HANDLER_NONE);
EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_END, th->self, 0, 0, 0, Qundef); EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_END, th->self, 0, 0, 0, Qundef);
} }
@ -856,9 +856,7 @@ thread_create_core(VALUE thval, VALUE args, VALUE (*fn)(void *))
th->invoke_type = thread_invoke_type_proc; th->invoke_type = thread_invoke_type_proc;
th->invoke_arg.proc.proc = rb_block_proc(); th->invoke_arg.proc.proc = rb_block_proc();
th->invoke_arg.proc.args = args; th->invoke_arg.proc.args = args;
th->invoke_arg.proc.kw_splat = rb_empty_keyword_given_p() ? th->invoke_arg.proc.kw_splat = rb_keyword_given_p();
RB_PASS_EMPTY_KEYWORDS :
rb_keyword_given_p();
} }
th->priority = current_th->priority; th->priority = current_th->priority;

7
vm.c
View file

@ -119,13 +119,6 @@ rb_vm_cframe_keyword_p(const rb_control_frame_t *cfp)
return VM_FRAME_CFRAME_KW_P(cfp); return VM_FRAME_CFRAME_KW_P(cfp);
} }
/* -- Remove In 3.0 -- */
int
rb_vm_cframe_empty_keyword_p(const rb_control_frame_t *cfp)
{
return VM_FRAME_CFRAME_EMPTY_KW_P(cfp);
}
VALUE VALUE
rb_vm_frame_block_handler(const rb_control_frame_t *cfp) rb_vm_frame_block_handler(const rb_control_frame_t *cfp)
{ {

383
vm_args.c
View file

@ -187,124 +187,6 @@ args_rest_array(struct args_info *args)
return ary; return ary;
} }
#define KW_HASH_HAS_NO_KEYS 0
#define KW_HASH_HAS_SYMBOL_KEY 1
#define KW_HASH_HAS_OTHER_KEY 2
#define KW_HASH_HAS_BOTH_KEYS 3
static int
keyword_hash_symbol_other_iter(st_data_t key, st_data_t val, st_data_t arg)
{
*(int*)arg |= SYMBOL_P((VALUE)key) ? KW_HASH_HAS_SYMBOL_KEY : KW_HASH_HAS_OTHER_KEY;
if ((*(int*)arg & KW_HASH_HAS_BOTH_KEYS) == KW_HASH_HAS_BOTH_KEYS) {
return ST_STOP;
}
return ST_CONTINUE;
}
static int
keyword_hash_symbol_other(VALUE hash)
{
int symbol_other = KW_HASH_HAS_NO_KEYS;
rb_hash_stlike_foreach(hash, keyword_hash_symbol_other_iter, (st_data_t)(&symbol_other));
return symbol_other;
}
static int
keyword_hash_split_iter(st_data_t key, st_data_t val, st_data_t arg)
{
if (SYMBOL_P((VALUE)key)) {
rb_hash_aset((VALUE)arg, (VALUE)key, (VALUE)val);
return ST_DELETE;
}
return ST_CONTINUE;
}
static void
keyword_hash_split(VALUE *kw_hash_ptr, VALUE *rest_hash_ptr)
{
*kw_hash_ptr = rb_hash_new();
rb_hash_stlike_foreach(*rest_hash_ptr, keyword_hash_split_iter, (st_data_t)(*kw_hash_ptr));
}
static int
keyword_hash_p(VALUE *kw_hash_ptr, VALUE *rest_hash_ptr, int check_only_symbol)
{
*rest_hash_ptr = rb_check_hash_type(*kw_hash_ptr);
if (!NIL_P(*rest_hash_ptr)) {
if (check_only_symbol) {
switch (keyword_hash_symbol_other(*rest_hash_ptr)) {
case KW_HASH_HAS_NO_KEYS:
case KW_HASH_HAS_SYMBOL_KEY:
break;
case KW_HASH_HAS_OTHER_KEY:
*kw_hash_ptr = Qnil;
return FALSE;
case KW_HASH_HAS_BOTH_KEYS:
*rest_hash_ptr = rb_hash_dup(*rest_hash_ptr);
keyword_hash_split(kw_hash_ptr, rest_hash_ptr);
return TRUE;
}
}
*kw_hash_ptr = *rest_hash_ptr;
*rest_hash_ptr = Qfalse;
return TRUE;
}
else {
*kw_hash_ptr = Qnil;
return FALSE;
}
}
static VALUE
args_pop_keyword_hash(struct args_info *args, VALUE *kw_hash_ptr, int check_only_symbol)
{
VALUE rest_hash;
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, &rest_hash, check_only_symbol)) {
if (rest_hash) {
args->argv[args->argc-1] = rest_hash;
}
else {
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, &rest_hash, check_only_symbol)) {
if (rest_hash) {
RARRAY_ASET(args->rest, len - 1, rest_hash);
}
else {
arg_rest_dup(args);
rb_ary_pop(args->rest);
return TRUE;
}
}
}
else {
goto from_argv;
}
}
return FALSE;
}
static int static int
args_kw_argv_to_hash(struct args_info *args) args_kw_argv_to_hash(struct args_info *args)
{ {
@ -572,149 +454,10 @@ fill_keys_values(st_data_t key, st_data_t val, st_data_t ptr)
static inline int static inline int
ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq) ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq)
{ {
if (!(iseq->body->param.flags.has_kw) && return !(iseq->body->param.flags.has_kw) &&
!(iseq->body->param.flags.has_kwrest)) { !(iseq->body->param.flags.has_kwrest) &&
keyword_hash = rb_check_hash_type(keyword_hash); RB_TYPE_P(keyword_hash, T_HASH) &&
RHASH_EMPTY_P(keyword_hash);
if (!NIL_P(keyword_hash) && RHASH_EMPTY_P(keyword_hash)) {
return 1;
}
}
return 0;
}
VALUE rb_iseq_location(const rb_iseq_t *iseq);
/* -- Remove In 3.0 -- */
/* This is a map from caller PC to a set of callee methods.
* When a warning about keyword argument change is printed,
* it keeps the pair of callee and caller.
*/
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_keyword_to_last_hash(rb_execution_context_t * const ec, struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq)
{
if (rb_warn_check(ec, iseq)) return;
VALUE name, loc;
if (calling->recv == Qundef) {
rb_warn("Passing the keyword argument as the last hash parameter is deprecated");
return;
}
name = rb_id2str(ci->mid);
loc = rb_iseq_location(iseq);
if (NIL_P(loc)) {
rb_warn("Passing the keyword argument for `%"PRIsVALUE"' as the last hash parameter is deprecated",
name);
}
else {
rb_warn("Passing the keyword argument as the last hash parameter is deprecated");
if (name) {
rb_compile_warn(RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1)),
"The called method `%"PRIsVALUE"' is defined here", name);
}
else {
rb_compile_warn(RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1)),
"The called method is defined here");
}
}
}
static inline void
rb_warn_split_last_hash_to_keyword(rb_execution_context_t * const ec, struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq)
{
if (rb_warn_check(ec, iseq)) return;
VALUE name, loc;
name = rb_id2str(ci->mid);
loc = rb_iseq_location(iseq);
if (NIL_P(loc)) {
rb_warn("Splitting the last argument for `%"PRIsVALUE"' into positional and keyword parameters is deprecated",
name);
}
else {
rb_warn("Splitting the last argument into positional and keyword parameters is deprecated");
if (calling->recv != Qundef) {
rb_compile_warn(RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1)),
"The called method `%"PRIsVALUE"' is defined here", name);
}
else {
rb_compile_warn(RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1)),
"The called method is defined here");
}
}
}
static inline void
rb_warn_last_hash_to_keyword(rb_execution_context_t * const ec, struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq)
{
if (rb_warn_check(ec, iseq)) return;
VALUE name, loc;
name = rb_id2str(ci->mid);
loc = rb_iseq_location(iseq);
if (NIL_P(loc)) {
rb_warn("Using the last argument for `%"PRIsVALUE"' as keyword parameters is deprecated; maybe ** should be added to the call",
name);
}
else {
rb_warn("Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call");
if (calling->recv != Qundef) {
rb_compile_warn(RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1)),
"The called method `%"PRIsVALUE"' is defined here", name);
}
else {
rb_compile_warn(RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1)),
"The called method is defined here");
}
}
} }
static int static int
@ -727,7 +470,6 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
const int max_argc = (iseq->body->param.flags.has_rest == FALSE) ? min_argc + iseq->body->param.opt_num : UNLIMITED_ARGUMENTS; const int max_argc = (iseq->body->param.flags.has_rest == FALSE) ? min_argc + iseq->body->param.opt_num : UNLIMITED_ARGUMENTS;
int opt_pc = 0; int opt_pc = 0;
int given_argc; int given_argc;
int kw_splat = FALSE;
unsigned int kw_flag = ci->flag & (VM_CALL_KWARG | VM_CALL_KW_SPLAT); unsigned int kw_flag = ci->flag & (VM_CALL_KWARG | VM_CALL_KW_SPLAT);
struct args_info args_body, *args; struct args_info args_body, *args;
VALUE keyword_hash = Qnil; VALUE keyword_hash = Qnil;
@ -813,46 +555,47 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
if (kw_flag & VM_CALL_KW_SPLAT) { if (kw_flag & VM_CALL_KW_SPLAT) {
if (len > 0 && ignore_keyword_hash_p(rest_last, iseq)) { if (len > 0 && ignore_keyword_hash_p(rest_last, iseq)) {
if (given_argc != min_argc) { if (remove_empty_keyword_hash) {
if (remove_empty_keyword_hash) { arg_rest_dup(args);
arg_rest_dup(args); rb_ary_pop(args->rest);
rb_ary_pop(args->rest); given_argc--;
given_argc--; kw_flag &= ~VM_CALL_KW_SPLAT;
kw_flag &= ~VM_CALL_KW_SPLAT;
}
else {
flag_keyword_hash = rest_last;
}
} }
else { else {
rb_warn_keyword_to_last_hash(ec, calling, ci, iseq); flag_keyword_hash = rest_last;
} }
} }
else if (!remove_empty_keyword_hash && rest_last) { else if (!remove_empty_keyword_hash && rest_last) {
flag_keyword_hash = rest_last; flag_keyword_hash = rest_last;
} }
else if (iseq->body->param.flags.has_kw || iseq->body->param.flags.has_kwrest) {
arg_rest_dup(args);
rb_ary_pop(args->rest);
given_argc--;
keyword_hash = rest_last;
}
} }
} }
else { else {
if (kw_flag & VM_CALL_KW_SPLAT) { if (kw_flag & VM_CALL_KW_SPLAT) {
VALUE last_arg = args->argv[args->argc-1]; VALUE last_arg = args->argv[args->argc-1];
if (ignore_keyword_hash_p(last_arg, iseq)) { if (ignore_keyword_hash_p(last_arg, iseq)) {
if (given_argc != min_argc) { if (remove_empty_keyword_hash) {
if (remove_empty_keyword_hash) { args->argc--;
args->argc--; given_argc--;
given_argc--; kw_flag &= ~VM_CALL_KW_SPLAT;
kw_flag &= ~VM_CALL_KW_SPLAT;
}
else {
flag_keyword_hash = last_arg;
}
} }
else { else {
rb_warn_keyword_to_last_hash(ec, calling, ci, iseq); flag_keyword_hash = last_arg;
} }
} }
else if (!remove_empty_keyword_hash) { else if (!remove_empty_keyword_hash) {
flag_keyword_hash = args->argv[args->argc-1]; flag_keyword_hash = last_arg;
}
else if (iseq->body->param.flags.has_kw || iseq->body->param.flags.has_kwrest) {
args->argc--;
given_argc--;
keyword_hash = last_arg;
} }
} }
args->rest = Qfalse; args->rest = Qfalse;
@ -870,7 +613,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
case arg_setup_method: case arg_setup_method:
break; /* do nothing special */ break; /* do nothing special */
case arg_setup_block: case arg_setup_block:
if (given_argc == 1 && if (given_argc == (keyword_hash == Qnil ? 1 : 2) &&
(min_argc > 0 || iseq->body->param.opt_num > 1 || (min_argc > 0 || iseq->body->param.opt_num > 1 ||
iseq->body->param.flags.has_kw || iseq->body->param.flags.has_kwrest) && iseq->body->param.flags.has_kw || iseq->body->param.flags.has_kwrest) &&
!iseq->body->param.flags.ambiguous_param0 && !iseq->body->param.flags.ambiguous_param0 &&
@ -882,60 +625,13 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
/* argc check */ /* argc check */
if (given_argc < min_argc) { if (given_argc < min_argc) {
if (given_argc == min_argc - 1 && args->kw_argv) { if (arg_setup_type == arg_setup_block) {
args_stored_kw_argv_to_hash(args); CHECK_VM_STACK_OVERFLOW(ec->cfp, min_argc);
given_argc = args_argc(args); given_argc = min_argc;
} args_extend(args, min_argc);
else {
if (arg_setup_type == arg_setup_block) {
CHECK_VM_STACK_OVERFLOW(ec->cfp, min_argc);
given_argc = min_argc;
args_extend(args, min_argc);
}
else {
argument_arity_error(ec, iseq, given_argc, min_argc, max_argc);
}
}
}
if (kw_flag & VM_CALL_KW_SPLAT) {
kw_splat = !iseq->body->param.flags.has_rest;
}
if ((iseq->body->param.flags.has_kw || iseq->body->param.flags.has_kwrest ||
(kw_splat && given_argc > max_argc)) &&
args->kw_argv == NULL) {
if (given_argc > min_argc) {
if (kw_flag) {
int check_only_symbol = (kw_flag & VM_CALL_KW_SPLAT) &&
iseq->body->param.flags.has_kw &&
!iseq->body->param.flags.has_kwrest;
if (args_pop_keyword_hash(args, &keyword_hash, check_only_symbol)) {
given_argc--;
}
else if (check_only_symbol) {
if (keyword_hash != Qnil) {
rb_warn_split_last_hash_to_keyword(ec, calling, ci, iseq);
}
else {
rb_warn_keyword_to_last_hash(ec, calling, ci, iseq);
}
}
}
else if (args_pop_keyword_hash(args, &keyword_hash, 1)) {
/* Warn the following:
* def foo(k:1) p [k]; end
* foo({k:42}) #=> 42
*/
rb_warn_last_hash_to_keyword(ec, calling, ci, iseq);
given_argc--;
}
else if (keyword_hash != Qnil) {
rb_warn_split_last_hash_to_keyword(ec, calling, ci, iseq);
}
} }
else if (given_argc == min_argc && kw_flag) { else {
rb_warn_keyword_to_last_hash(ec, calling, ci, iseq); argument_arity_error(ec, iseq, given_argc, min_argc, max_argc);
} }
} }
@ -1160,8 +856,6 @@ refine_sym_proc_call(RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, callback_arg))
const VALUE symbol = RARRAY_AREF(callback_arg, 0); const VALUE symbol = RARRAY_AREF(callback_arg, 0);
const VALUE refinements = RARRAY_AREF(callback_arg, 1); const VALUE refinements = RARRAY_AREF(callback_arg, 1);
int kw_splat = RB_PASS_CALLED_KEYWORDS; int kw_splat = RB_PASS_CALLED_KEYWORDS;
VALUE v;
VALUE ret;
VALUE klass; VALUE klass;
if (argc-- < 1) { if (argc-- < 1) {
@ -1182,15 +876,10 @@ refine_sym_proc_call(RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, callback_arg))
if (!NIL_P(blockarg)) { if (!NIL_P(blockarg)) {
vm_passed_block_handler_set(ec, blockarg); vm_passed_block_handler_set(ec, blockarg);
} }
v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat);
if (!me) { if (!me) {
ret = method_missing(obj, mid, argc, argv, MISSING_NOENTRY, kw_splat); return method_missing(obj, mid, argc, argv, MISSING_NOENTRY, kw_splat);
} }
else { return rb_vm_call0(ec, obj, mid, argc, argv, me, kw_splat);
ret = rb_vm_call0(ec, obj, mid, argc, argv, me, kw_splat);
}
rb_free_tmp_buffer(&v);
return ret;
} }
static VALUE static VALUE

View file

@ -1159,11 +1159,11 @@ typedef rb_control_frame_t *
enum { enum {
/* Frame/Environment flag bits: /* Frame/Environment flag bits:
* MMMM MMMM MMMM MMMM ____ FFFF FFFF EEEX (LSB) * MMMM MMMM MMMM MMMM ____ _FFF FFFF EEEX (LSB)
* *
* X : tag for GC marking (It seems as Fixnum) * X : tag for GC marking (It seems as Fixnum)
* EEE : 3 bits Env flags * EEE : 3 bits Env flags
* FF..: 8 bits Frame flags * FF..: 7 bits Frame flags
* MM..: 15 bits frame magic (to check frame corruption) * MM..: 15 bits frame magic (to check frame corruption)
*/ */
@ -1188,7 +1188,6 @@ enum {
VM_FRAME_FLAG_LAMBDA = 0x0100, VM_FRAME_FLAG_LAMBDA = 0x0100,
VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM = 0x0200, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM = 0x0200,
VM_FRAME_FLAG_CFRAME_KW = 0x0400, VM_FRAME_FLAG_CFRAME_KW = 0x0400,
VM_FRAME_FLAG_CFRAME_EMPTY_KW = 0x0800, /* -- Remove In 3.0 -- */
/* env flag */ /* env flag */
VM_ENV_FLAG_LOCAL = 0x0002, VM_ENV_FLAG_LOCAL = 0x0002,
@ -1249,13 +1248,6 @@ VM_FRAME_CFRAME_KW_P(const rb_control_frame_t *cfp)
return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_CFRAME_KW) != 0; return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_CFRAME_KW) != 0;
} }
/* -- Remove In 3.0 -- */
static inline int
VM_FRAME_CFRAME_EMPTY_KW_P(const rb_control_frame_t *cfp)
{
return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_CFRAME_EMPTY_KW) != 0;
}
static inline int static inline int
VM_FRAME_FINISHED_P(const rb_control_frame_t *cfp) VM_FRAME_FINISHED_P(const rb_control_frame_t *cfp)
{ {

106
vm_eval.c
View file

@ -69,7 +69,6 @@ vm_call0_cfunc_with_frame(rb_execution_context_t* ec, struct rb_calling_info *ca
if (calling->kw_splat) { if (calling->kw_splat) {
if (argc > 0 && RB_TYPE_P(argv[argc-1], T_HASH) && RHASH_EMPTY_P(argv[argc-1])) { if (argc > 0 && RB_TYPE_P(argv[argc-1], T_HASH) && RHASH_EMPTY_P(argv[argc-1])) {
frame_flags |= VM_FRAME_FLAG_CFRAME_EMPTY_KW;
argc--; argc--;
} }
else { else {
@ -144,12 +143,7 @@ vm_call0_body(rb_execution_context_t *ec, struct rb_calling_info *calling, struc
calling->argc > 0 && calling->argc > 0 &&
RB_TYPE_P(argv[calling->argc-1], T_HASH) && RB_TYPE_P(argv[calling->argc-1], T_HASH) &&
RHASH_EMPTY_P(argv[calling->argc-1])) { RHASH_EMPTY_P(argv[calling->argc-1])) {
if (calling->argc == 1) { calling->argc--;
rb_warn("Passing the keyword argument as the last hash parameter is deprecated");
}
else {
calling->argc--;
}
} }
rb_check_arity(calling->argc, 1, 1); rb_check_arity(calling->argc, 1, 1);
@ -232,42 +226,10 @@ vm_call0_body(rb_execution_context_t *ec, struct rb_calling_info *calling, struc
return ret; return ret;
} }
/* Caller should keep the reference to the return value until argv becomes useless. */
MJIT_FUNC_EXPORTED VALUE
rb_adjust_argv_kw_splat(int *argc, const VALUE **argv, int *kw_splat)
{
if (*kw_splat == RB_PASS_CALLED_KEYWORDS || *kw_splat == RB_PASS_EMPTY_KEYWORDS) {
if (*kw_splat == RB_PASS_EMPTY_KEYWORDS || rb_empty_keyword_given_p()) {
int n = *argc;
VALUE v;
VALUE *ptr = rb_alloc_tmp_buffer2(&v, n+1, sizeof(VALUE));
if (n) memcpy(ptr, *argv, sizeof(VALUE)*n);
ptr[n] = rb_hash_new();
*argc = ++n;
*argv = ptr;
*kw_splat = 1;
return v;
}
else {
*kw_splat = rb_keyword_given_p();
}
}
if (*kw_splat && (*argc == 0 || !RB_TYPE_P((*argv)[(*argc)-1], T_HASH))) {
rb_warn("Keyword flag passed calling internal method, but last entry is not a hash, unsetting keyword flag");
*kw_splat = 0;
}
return 0;
}
MJIT_FUNC_EXPORTED VALUE MJIT_FUNC_EXPORTED VALUE
rb_vm_call_kw(rb_execution_context_t *ec, VALUE recv, VALUE id, int argc, const VALUE *argv, const rb_callable_method_entry_t *me, int kw_splat) rb_vm_call_kw(rb_execution_context_t *ec, VALUE recv, VALUE id, int argc, const VALUE *argv, const rb_callable_method_entry_t *me, int kw_splat)
{ {
VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); return rb_vm_call0(ec, recv, id, argc, argv, me, kw_splat);
VALUE ret = rb_vm_call0(ec, recv, id, argc, argv, me, kw_splat);
rb_free_tmp_buffer(&v);
return ret;
} }
static inline VALUE static inline VALUE
@ -961,10 +923,7 @@ rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv)
VALUE VALUE
rb_funcallv_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat) rb_funcallv_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat)
{ {
VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); return rb_call(recv, mid, argc, argv, kw_splat ? CALL_FCALL_KW : CALL_FCALL);
VALUE ret = rb_call(recv, mid, argc, argv, kw_splat ? CALL_FCALL_KW : CALL_FCALL);
rb_free_tmp_buffer(&v);
return ret;
} }
/*! /*!
@ -985,10 +944,7 @@ rb_funcallv_public(VALUE recv, ID mid, int argc, const VALUE *argv)
VALUE VALUE
rb_funcallv_public_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat) rb_funcallv_public_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat)
{ {
VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); return rb_call(recv, mid, argc, argv, kw_splat ? CALL_PUBLIC_KW : CALL_PUBLIC);
VALUE ret = rb_call(recv, mid, argc, argv, kw_splat ? CALL_PUBLIC_KW : CALL_PUBLIC);
rb_free_tmp_buffer(&v);
return ret;
} }
/*! /*!
@ -1038,12 +994,8 @@ rb_funcall_passing_block(VALUE recv, ID mid, int argc, const VALUE *argv)
VALUE VALUE
rb_funcall_passing_block_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat) rb_funcall_passing_block_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat)
{ {
VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat);
VALUE ret;
PASS_PASSED_BLOCK_HANDLER(); PASS_PASSED_BLOCK_HANDLER();
ret = rb_call(recv, mid, argc, argv, kw_splat ? CALL_PUBLIC_KW : CALL_PUBLIC); return rb_call(recv, mid, argc, argv, kw_splat ? CALL_PUBLIC_KW : CALL_PUBLIC);
rb_free_tmp_buffer(&v);
return ret;
} }
VALUE VALUE
@ -1063,10 +1015,7 @@ rb_funcall_with_block_kw(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE
vm_passed_block_handler_set(GET_EC(), passed_procval); vm_passed_block_handler_set(GET_EC(), passed_procval);
} }
VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); return rb_call(recv, mid, argc, argv, kw_splat ? CALL_PUBLIC_KW : CALL_PUBLIC);
VALUE ret = rb_call(recv, mid, argc, argv, kw_splat ? CALL_PUBLIC_KW : CALL_PUBLIC);
rb_free_tmp_buffer(&v);
return ret;
} }
static VALUE * static VALUE *
@ -1140,10 +1089,7 @@ send_internal(int argc, const VALUE *argv, VALUE recv, call_type scope)
static VALUE static VALUE
send_internal_kw(int argc, const VALUE *argv, VALUE recv, call_type scope) send_internal_kw(int argc, const VALUE *argv, VALUE recv, call_type scope)
{ {
VALUE v=0, ret; if (rb_keyword_given_p()) {
int kw_splat = RB_PASS_CALLED_KEYWORDS;
v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat);
if (kw_splat) {
switch (scope) { switch (scope) {
case CALL_PUBLIC: case CALL_PUBLIC:
scope = CALL_PUBLIC_KW; scope = CALL_PUBLIC_KW;
@ -1155,9 +1101,7 @@ send_internal_kw(int argc, const VALUE *argv, VALUE recv, call_type scope)
break; break;
} }
} }
ret = send_internal(argc, argv, recv, scope); return send_internal(argc, argv, recv, scope);
rb_free_tmp_buffer(&v);
return ret;
} }
/* /*
@ -1215,10 +1159,7 @@ rb_f_public_send(int argc, VALUE *argv, VALUE recv)
static inline VALUE static inline VALUE
rb_yield_0_kw(int argc, const VALUE * argv, int kw_splat) rb_yield_0_kw(int argc, const VALUE * argv, int kw_splat)
{ {
VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); return vm_yield(GET_EC(), argc, argv, kw_splat);
VALUE ret = vm_yield(GET_EC(), argc, argv, kw_splat);
rb_free_tmp_buffer(&v);
return ret;
} }
static inline VALUE static inline VALUE
@ -1314,13 +1255,9 @@ rb_yield_force_blockarg(VALUE values)
VALUE VALUE
rb_yield_block(RB_BLOCK_CALL_FUNC_ARGLIST(val, arg)) rb_yield_block(RB_BLOCK_CALL_FUNC_ARGLIST(val, arg))
{ {
int kw_splat = RB_PASS_CALLED_KEYWORDS; return vm_yield_with_block(GET_EC(), argc, argv,
VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); NIL_P(blockarg) ? VM_BLOCK_HANDLER_NONE : blockarg,
VALUE ret = vm_yield_with_block(GET_EC(), argc, argv, rb_keyword_given_p());
NIL_P(blockarg) ? VM_BLOCK_HANDLER_NONE : blockarg,
kw_splat);
rb_free_tmp_buffer(&v);
return ret;
} }
static VALUE static VALUE
@ -1486,15 +1423,12 @@ rb_block_call_kw(VALUE obj, ID mid, int argc, const VALUE * argv,
{ {
struct iter_method_arg arg; struct iter_method_arg arg;
VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat);
arg.obj = obj; arg.obj = obj;
arg.mid = mid; arg.mid = mid;
arg.argc = argc; arg.argc = argc;
arg.argv = argv; arg.argv = argv;
arg.kw_splat = kw_splat; arg.kw_splat = kw_splat;
VALUE ret = rb_iterate(iterate_method, (VALUE)&arg, bl_proc, data2); return rb_iterate(iterate_method, (VALUE)&arg, bl_proc, data2);
rb_free_tmp_buffer(&v);
return ret;
} }
VALUE VALUE
@ -1839,9 +1773,6 @@ yield_under(VALUE under, VALUE self, int argc, const VALUE *argv, int kw_splat)
const VALUE *ep = NULL; const VALUE *ep = NULL;
rb_cref_t *cref; rb_cref_t *cref;
int is_lambda = FALSE; int is_lambda = FALSE;
VALUE v = 0, ret;
v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat);
if (block_handler != VM_BLOCK_HANDLER_NONE) { if (block_handler != VM_BLOCK_HANDLER_NONE) {
again: again:
@ -1861,10 +1792,9 @@ yield_under(VALUE under, VALUE self, int argc, const VALUE *argv, int kw_splat)
block_handler = vm_proc_to_block_handler(VM_BH_TO_PROC(block_handler)); block_handler = vm_proc_to_block_handler(VM_BH_TO_PROC(block_handler));
goto again; goto again;
case block_handler_type_symbol: case block_handler_type_symbol:
ret = rb_sym_proc_call(SYM2ID(VM_BH_TO_SYMBOL(block_handler)), return rb_sym_proc_call(SYM2ID(VM_BH_TO_SYMBOL(block_handler)),
argc, argv, kw_splat, VM_BLOCK_HANDLER_NONE); argc, argv, kw_splat,
rb_free_tmp_buffer(&v); VM_BLOCK_HANDLER_NONE);
return ret;
} }
new_captured.self = self; new_captured.self = self;
@ -1874,9 +1804,7 @@ yield_under(VALUE under, VALUE self, int argc, const VALUE *argv, int kw_splat)
} }
cref = vm_cref_push(ec, under, ep, TRUE); cref = vm_cref_push(ec, under, ep, TRUE);
ret = vm_yield_with_cref(ec, argc, argv, kw_splat, cref, is_lambda); return vm_yield_with_cref(ec, argc, argv, kw_splat, cref, is_lambda);
rb_free_tmp_buffer(&v);
return ret;
} }
VALUE VALUE

View file

@ -2486,9 +2486,8 @@ vm_method_cfunc_entry(const rb_callable_method_entry_t *me)
return UNALIGNED_MEMBER_PTR(me->def, body.cfunc); return UNALIGNED_MEMBER_PTR(me->def, body.cfunc);
} }
/* -- Remove empty_kw_splat In 3.0 -- */
static VALUE static VALUE
vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, struct rb_call_data *cd, int empty_kw_splat) vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, struct rb_call_data *cd)
{ {
const struct rb_call_info *ci = &cd->ci; const struct rb_call_info *ci = &cd->ci;
const struct rb_call_cache *cc = &cd->cc; const struct rb_call_cache *cc = &cd->cc;
@ -2506,9 +2505,6 @@ vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp
if (UNLIKELY(calling->kw_splat)) { if (UNLIKELY(calling->kw_splat)) {
frame_type |= VM_FRAME_FLAG_CFRAME_KW; frame_type |= VM_FRAME_FLAG_CFRAME_KW;
} }
else if (UNLIKELY(empty_kw_splat)) {
frame_type |= VM_FRAME_FLAG_CFRAME_EMPTY_KW;
}
RUBY_DTRACE_CMETHOD_ENTRY_HOOK(ec, me->owner, me->def->original_id); RUBY_DTRACE_CMETHOD_ENTRY_HOOK(ec, me->owner, me->def->original_id);
EXEC_EVENT_HOOK(ec, RUBY_EVENT_C_CALL, recv, me->def->original_id, ci->mid, me->owner, Qundef); EXEC_EVENT_HOOK(ec, RUBY_EVENT_C_CALL, recv, me->def->original_id, ci->mid, me->owner, Qundef);
@ -2536,16 +2532,11 @@ static VALUE
vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, struct rb_call_data *cd) vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, struct rb_call_data *cd)
{ {
const struct rb_call_info *ci = &cd->ci; const struct rb_call_info *ci = &cd->ci;
int empty_kw_splat;
RB_DEBUG_COUNTER_INC(ccf_cfunc); RB_DEBUG_COUNTER_INC(ccf_cfunc);
CALLER_SETUP_ARG(reg_cfp, calling, ci); CALLER_SETUP_ARG(reg_cfp, calling, ci);
empty_kw_splat = calling->kw_splat;
CALLER_REMOVE_EMPTY_KW_SPLAT(reg_cfp, calling, ci); CALLER_REMOVE_EMPTY_KW_SPLAT(reg_cfp, calling, ci);
if (empty_kw_splat && calling->kw_splat) { return vm_call_cfunc_with_frame(ec, reg_cfp, calling, cd);
empty_kw_splat = 0;
}
return vm_call_cfunc_with_frame(ec, reg_cfp, calling, cd, empty_kw_splat);
} }
static VALUE static VALUE
@ -2935,12 +2926,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
case VM_METHOD_TYPE_ATTRSET: case VM_METHOD_TYPE_ATTRSET:
CALLER_SETUP_ARG(cfp, calling, ci); CALLER_SETUP_ARG(cfp, calling, ci);
if (calling->argc == 1 && calling->kw_splat && RHASH_EMPTY_P(cfp->sp[-1])) { CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
rb_warn_keyword_to_last_hash(ec, calling, ci, NULL);
}
else {
CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
}
rb_check_arity(calling->argc, 1, 1); rb_check_arity(calling->argc, 1, 1);
cc->aux.index = 0; cc->aux.index = 0;
@ -3211,13 +3197,8 @@ vm_yield_with_cfunc(rb_execution_context_t *ec,
blockarg = rb_vm_bh_to_procval(ec, block_handler); blockarg = rb_vm_bh_to_procval(ec, block_handler);
frame_flag = VM_FRAME_MAGIC_IFUNC | VM_FRAME_FLAG_CFRAME | (me ? VM_FRAME_FLAG_BMETHOD : 0); frame_flag = VM_FRAME_MAGIC_IFUNC | VM_FRAME_FLAG_CFRAME | (me ? VM_FRAME_FLAG_BMETHOD : 0);
switch (kw_splat) { if (kw_splat) {
case 1: frame_flag |= VM_FRAME_FLAG_CFRAME_KW;
frame_flag |= VM_FRAME_FLAG_CFRAME_KW;
break;
case 2:
frame_flag |= VM_FRAME_FLAG_CFRAME_EMPTY_KW;
break;
} }
vm_push_frame(ec, (const rb_iseq_t *)captured->code.ifunc, vm_push_frame(ec, (const rb_iseq_t *)captured->code.ifunc,
@ -3274,12 +3255,7 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
VALUE arg0; VALUE arg0;
CALLER_SETUP_ARG(cfp, calling, ci); CALLER_SETUP_ARG(cfp, calling, ci);
if (calling->kw_splat && calling->argc == iseq->body->param.lead_num + iseq->body->param.post_num && RHASH_EMPTY_P(cfp->sp[-1])) { CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
rb_warn_keyword_to_last_hash(ec, calling, ci, iseq);
}
else {
CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
}
if (arg_setup_type == arg_setup_block && if (arg_setup_type == arg_setup_block &&
calling->argc == 1 && calling->argc == 1 &&
@ -3377,17 +3353,10 @@ vm_invoke_ifunc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
{ {
VALUE val; VALUE val;
int argc; int argc;
int kw_splat = calling->kw_splat;
CALLER_SETUP_ARG(ec->cfp, calling, ci); CALLER_SETUP_ARG(ec->cfp, calling, ci);
CALLER_REMOVE_EMPTY_KW_SPLAT(ec->cfp, calling, ci); CALLER_REMOVE_EMPTY_KW_SPLAT(ec->cfp, calling, ci);
if (kw_splat && !calling->kw_splat) {
kw_splat = 2;
}
else {
kw_splat = calling->kw_splat;
}
argc = calling->argc; argc = calling->argc;
val = vm_yield_with_cfunc(ec, captured, captured->self, argc, STACK_ADDR_FROM_TOP(argc), kw_splat, calling->block_handler, NULL); val = vm_yield_with_cfunc(ec, captured, captured->self, argc, STACK_ADDR_FROM_TOP(argc), calling->kw_splat, calling->block_handler, NULL);
POPN(argc); /* TODO: should put before C/yield? */ POPN(argc); /* TODO: should put before C/yield? */
return val; return val;
} }