aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--test/ruby/test_keyword.rb118
-rw-r--r--vm_args.c46
-rw-r--r--vm_insnhelper.c27
3 files changed, 166 insertions, 25 deletions
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index b337343420..81ac432979 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -177,6 +177,100 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(["bar", 111111], f[str: "bar", num: 111111])
end
+ def test_regular_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ assert_equal([], c.m(**{}))
+ assert_equal([], c.m(**kw))
+ assert_equal([h], c.m(**h))
+ assert_equal([h], c.m(a: 1))
+ assert_equal([h2], c.m(**h2))
+ assert_equal([h3], c.m(**h3))
+ assert_equal([h3], c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m; end
+ assert_nil(c.m(**{}))
+ assert_nil(c.m(**kw))
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ assert_equal(kw, c.m(**{}))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ assert_equal(kw, c.m(**kw))
+ end
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, c.m(**{}))
+ assert_equal(kw, c.m(**kw))
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ c.m(**{})
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ c.m(**kw)
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ assert_equal([h, kw], c.m(**h))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ assert_equal([h, kw], c.m(a: 1))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ assert_equal([h2, kw], c.m(**h2))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ assert_equal([h3, kw], c.m(**h3))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ assert_equal([h3, kw], c.m(a: 1, **h2))
+ end
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg=1, args]
+ end
+ assert_equal([1, kw], c.m(**{}))
+ assert_equal([1, kw], c.m(**kw))
+ assert_equal([1, h], c.m(**h))
+ assert_equal([1, h], c.m(a: 1))
+ assert_equal([1, h2], c.m(**h2))
+ assert_equal([1, h3], c.m(**h3))
+ assert_equal([1, h3], c.m(a: 1, **h2))
+ end
+
def test_lambda_kwsplat_call
kw = {}
h = {:a=>1}
@@ -459,8 +553,12 @@ class TestKeywordArguments < Test::Unit::TestCase
def c.m(args)
args
end
- assert_raise(ArgumentError) { c.send(:m, **{}) }
- assert_raise(ArgumentError) { c.send(:m, **kw) }
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ assert_equal(kw, c.send(:m, **{}))
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ assert_equal(kw, c.send(:m, **kw))
+ end
assert_equal(h, c.send(:m, **h))
assert_equal(h, c.send(:m, a: 1))
assert_equal(h2, c.send(:m, **h2))
@@ -482,8 +580,12 @@ class TestKeywordArguments < Test::Unit::TestCase
def c.m(arg, **args)
[arg, args]
end
- assert_raise(ArgumentError) { c.send(:m, **{}) }
- assert_raise(ArgumentError) { c.send(:m, **kw) }
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ c.send(:m, **{})
+ end
+ assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
+ c.send(:m, **kw)
+ end
assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do
assert_equal([h, kw], c.send(:m, **h))
end
@@ -824,8 +926,12 @@ class TestKeywordArguments < Test::Unit::TestCase
class << c
attr_writer :m
end
- assert_raise(ArgumentError) { c.send(:m=, **{}) }
- assert_raise(ArgumentError) { c.send(:m=, **kw) }
+ assert_warn(/The keyword argument for `m=' is passed as the last hash parameter/) do
+ c.send(:m=, **{})
+ end
+ assert_warn(/The keyword argument for `m=' is passed as the last hash parameter/) do
+ c.send(:m=, **kw)
+ end
assert_equal(h, c.send(:m=, **h))
assert_equal(h, c.send(:m=, a: 1))
assert_equal(h2, c.send(:m=, **h2))
diff --git a/vm_args.c b/vm_args.c
index 5f8eb12869..e9a11bd255 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -581,15 +581,16 @@ ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq) {
VALUE rb_iseq_location(const rb_iseq_t *iseq);
-static inline void
+void
rb_warn_keyword_to_last_hash(struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq)
{
+ VALUE name, loc;
if (calling->recv == Qundef) {
rb_warn("The keyword argument is passed as the last hash parameter");
return;
}
- VALUE name = rb_id2str(ci->mid);
- VALUE loc = rb_iseq_location(iseq);
+ name = rb_id2str(ci->mid);
+ loc = rb_iseq_location(iseq);
if (NIL_P(loc)) {
rb_warn("The keyword argument for `%"PRIsVALUE"' is passed as the last hash parameter",
name);
@@ -604,9 +605,10 @@ rb_warn_keyword_to_last_hash(struct rb_calling_info *calling, const struct rb_ca
static inline void
rb_warn_split_last_hash_to_keyword(struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq)
{
+ VALUE name, loc;
if (calling->recv == Qundef) return;
- VALUE name = rb_id2str(ci->mid);
- VALUE loc = rb_iseq_location(iseq);
+ name = rb_id2str(ci->mid);
+ loc = rb_iseq_location(iseq);
if (NIL_P(loc)) {
rb_warn("The last argument for `%"PRIsVALUE"' is split into positional and keyword parameters",
name);
@@ -621,9 +623,10 @@ rb_warn_split_last_hash_to_keyword(struct rb_calling_info *calling, const struct
static inline void
rb_warn_last_hash_to_keyword(struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq)
{
+ VALUE name, loc;
if (calling->recv == Qundef) return;
- VALUE name = rb_id2str(ci->mid);
- VALUE loc = rb_iseq_location(iseq);
+ name = rb_id2str(ci->mid);
+ loc = rb_iseq_location(iseq);
if (NIL_P(loc)) {
rb_warn("The last argument for `%"PRIsVALUE"' is used as the keyword parameter",
name);
@@ -651,6 +654,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
VALUE keyword_hash = Qnil;
VALUE * const orig_sp = ec->cfp->sp;
unsigned int i;
+ int remove_empty_keyword_hash = 1;
vm_check_canary(ec, orig_sp);
/*
@@ -700,6 +704,10 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
args->kw_argv = NULL;
}
+ if (given_argc == min_argc) {
+ remove_empty_keyword_hash = 0;
+ }
+
if (ci->flag & VM_CALL_ARGS_SPLAT) {
args->rest = locals[--args->argc];
args->rest_index = 0;
@@ -707,19 +715,29 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
if (kw_flag & VM_CALL_KW_SPLAT) {
int len = RARRAY_LENINT(args->rest);
if (len > 0 && ignore_keyword_hash_p(RARRAY_AREF(args->rest, len - 1), iseq)) {
- arg_rest_dup(args);
- rb_ary_pop(args->rest);
- given_argc--;
- kw_flag &= ~VM_CALL_KW_SPLAT;
+ if (remove_empty_keyword_hash) {
+ arg_rest_dup(args);
+ rb_ary_pop(args->rest);
+ given_argc--;
+ kw_flag &= ~VM_CALL_KW_SPLAT;
+ }
+ else {
+ rb_warn_keyword_to_last_hash(calling, ci, iseq);
+ }
}
}
}
else {
if (kw_flag & VM_CALL_KW_SPLAT) {
if (ignore_keyword_hash_p(args->argv[args->argc-1], iseq)) {
- args->argc--;
- given_argc--;
- kw_flag &= ~VM_CALL_KW_SPLAT;
+ if (remove_empty_keyword_hash) {
+ args->argc--;
+ given_argc--;
+ kw_flag &= ~VM_CALL_KW_SPLAT;
+ }
+ else {
+ rb_warn_keyword_to_last_hash(calling, ci, iseq);
+ }
}
}
args->rest = Qfalse;
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 4ae94e3fd7..fe333d1ea1 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -24,6 +24,7 @@ extern void rb_method_definition_set(const rb_method_entry_t *me, rb_method_defi
extern int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2);
extern VALUE rb_make_no_method_exception(VALUE exc, VALUE format, VALUE obj,
int argc, const VALUE *argv, int priv);
+extern void rb_warn_keyword_to_last_hash(struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq);
/* control stack frame */
@@ -1738,9 +1739,9 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq)
static inline void
-CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
- struct rb_calling_info *restrict calling,
- const struct rb_call_info *restrict ci)
+CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(struct rb_control_frame_struct *restrict cfp,
+ struct rb_calling_info *restrict calling,
+ const struct rb_call_info *restrict ci)
{
if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
/* This expands the rest argument to the stack.
@@ -1755,6 +1756,15 @@ CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
*/
vm_caller_setup_arg_kw(cfp, calling, ci);
}
+}
+
+static inline void
+CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
+ struct rb_calling_info *restrict calling,
+ const struct rb_call_info *restrict ci)
+{
+ CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(cfp, calling, ci);
+
if (UNLIKELY(calling->kw_splat)) {
/* This removes the last Hash object if it is empty.
* So, ci->flag & VM_CALL_KW_SPLAT is now inconsistent.
@@ -2314,7 +2324,7 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct
struct rb_call_info_with_kwarg ci_entry;
struct rb_call_cache cc_entry, *cc;
- CALLER_SETUP_ARG(reg_cfp, calling, orig_ci);
+ CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(reg_cfp, calling, orig_ci);
i = calling->argc - 1;
@@ -2624,7 +2634,14 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
return vm_call_cfunc(ec, cfp, calling, ci, cc);
case VM_METHOD_TYPE_ATTRSET:
- CALLER_SETUP_ARG(cfp, calling, ci);
+ if (calling->argc == 1 && calling->kw_splat && RHASH_EMPTY_P(cfp->sp[-1])) {
+ CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(cfp, calling, ci);
+ rb_warn_keyword_to_last_hash(calling, ci, NULL);
+ }
+ else {
+ CALLER_SETUP_ARG(cfp, calling, ci);
+ }
+
rb_check_arity(calling->argc, 1, 1);
cc->aux.index = 0;
CC_SET_FASTPATH(cc, vm_call_attrset, !((ci->flag & VM_CALL_ARGS_SPLAT) || (ci->flag & VM_CALL_KWARG)));