diff options
-rw-r--r-- | compile.c | 30 | ||||
-rw-r--r-- | parse.y | 10 | ||||
-rw-r--r-- | test/ruby/test_keyword.rb | 12 |
3 files changed, 41 insertions, 11 deletions
@@ -3940,6 +3940,8 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node_ro else { int opt_p = 1; int first = 1, i; + int single_kw = 0; + int num_kw = 0; while (node) { const NODE *start_node = node, *end_node; @@ -3955,11 +3957,15 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node_ro if (type == COMPILE_ARRAY_TYPE_HASH && !node->nd_head) { kw = node->nd_next; + num_kw++; node = 0; if (kw) { opt_p = 0; node = kw->nd_next; kw = kw->nd_head; + if (!single_kw && !node) { + single_kw = 1; + } } break; } @@ -4053,6 +4059,7 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node_ro } case COMPILE_ARRAY_TYPE_HASH: if (i > 0) { + num_kw++; if (first) { if (!popped) { ADD_INSN1(anchor, line, newhash, INT2FIX(i)); @@ -4071,16 +4078,27 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node_ro } } if (kw) { - if (!popped) { - ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - if (i > 0 || !first) ADD_INSN(ret, line, swap); - else ADD_INSN1(ret, line, newhash, INT2FIX(0)); + int empty_kw = nd_type(kw) == NODE_LIT; + int first_kw = num_kw == 1; + int only_kw = single_kw && first_kw; + + if (!popped && !empty_kw) { + ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + if (i > 0 || !first) ADD_INSN(ret, line, swap); + else ADD_INSN1(ret, line, newhash, INT2FIX(0)); } - NO_CHECK(COMPILE(ret, "keyword splat", kw)); + + if (empty_kw && first_kw && !only_kw) { + ADD_INSN1(ret, line, newhash, INT2FIX(0)); + } + else if (!empty_kw || only_kw) { + NO_CHECK(COMPILE(ret, "keyword splat", kw)); + } + if (popped) { ADD_INSN(ret, line, pop); } - else { + else if (!empty_kw) { ADD_SEND(ret, line, id_core_hash_merge_kwd, INT2FIX(2)); } } @@ -5209,8 +5209,14 @@ assoc : arg_value tASSOC arg_value { /*%%%*/ if (nd_type($2) == NODE_HASH && - !($2->nd_head && $2->nd_head->nd_alen)) - $$ = 0; + !($2->nd_head && $2->nd_head->nd_alen)) { + static VALUE empty_hash; + if (!empty_hash) { + empty_hash = rb_obj_freeze(rb_hash_new()); + rb_gc_register_mark_object(empty_hash); + } + $$ = list_append(p, NEW_LIST(0, &@$), NEW_LIT(empty_hash, &@$)); + } else $$ = list_append(p, NEW_LIST(0, &@$), $2); /*% %*/ diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 1cf905ff01..c3ad7b9a97 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -205,7 +205,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h3, f[**h3]) f = ->(a, **x) { [a,x] } - assert_raise(ArgumentError) { f[**{}] } + assert_warn(/The keyword argument is passed as the last hash parameter.* for `\[\]'/m) do + assert_equal([{}, {}], f[**{}]) + end assert_warn(/The keyword argument is passed as the last hash parameter.* for `\[\]'/m) do assert_equal([{}, {}], f[**kw]) end @@ -418,7 +420,9 @@ class TestKeywordArguments < Test::Unit::TestCase def c.m(arg, **args) [arg, args] end - assert_raise(ArgumentError) { c.send(:m, **{}) } + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([kw, kw], c.send(:m, **{})) + end assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([kw, kw], c.send(:m, **kw)) end @@ -491,7 +495,9 @@ class TestKeywordArguments < Test::Unit::TestCase def c.method_missing(_, arg, **args) [arg, args] end - assert_raise(ArgumentError) { c.send(:m, **{}) } + assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do + assert_equal([kw, kw], c.send(:m, **{})) + end assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([kw, kw], c.send(:m, **kw)) end |