mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Make m(**{}) mean call without keywords
Previously, **{} was removed by the parser: ``` $ ruby --dump=parse -e '{**{}}' @ NODE_SCOPE (line: 1, location: (1,0)-(1,6)) +- nd_tbl: (empty) +- nd_args: | (null node) +- nd_body: @ NODE_HASH (line: 1, location: (1,0)-(1,6))* +- nd_brace: 1 (hash literal) +- nd_head: (null node) ``` Since it was removed by the parser, the compiler did not know about it, and `m(**{})` was therefore treated as `m()`. This modifies the parser to not remove the `**{}`. A simple approach for this is fairly simple by just removing a few lines from the parser, but that would cause two hash allocations every time it was used. The approach taken here modifies both the parser and the compiler, and results in `**{}` not allocating any hashes in the usual case. The basic idea is we use a literal node in the parser containing a frozen empty hash literal. In the compiler, we recognize when that is used, and if it is the only keyword present, we just push it onto the VM stack (no creation of a new hash or merging of keywords). If it is the first keyword present, we push a new empty hash onto the VM stack, so that later keywords can merge into it. If it is not the first keyword present, we can ignore it, since the there is no reason to merge an empty hash into the existing hash. Example instructions for `m(**{})` Before (note ARGS_SIMPLE): ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,7)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 opt_send_without_block <callinfo!mid:m, argc:0, FCALL|ARGS_SIMPLE>, <callcache> 0004 leave ``` After (note putobject and KW_SPLAT): ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,7)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 putobject {} 0003 opt_send_without_block <callinfo!mid:m, argc:1, FCALL|KW_SPLAT>, <callcache> 0006 leave ``` Example instructions for `m(**h, **{})` Before and After (no change): ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,12)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 putspecialobject 1 0003 newhash 0 0005 putself 0006 opt_send_without_block <callinfo!mid:h, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0009 opt_send_without_block <callinfo!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE>, <callcache> 0012 opt_send_without_block <callinfo!mid:m, argc:1, FCALL|KW_SPLAT>, <callcache> 0015 leave ``` Example instructions for `m(**{}, **h)` Before: ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,12)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 putspecialobject 1 0003 newhash 0 0005 putself 0006 opt_send_without_block <callinfo!mid:h, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0009 opt_send_without_block <callinfo!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE>, <callcache> 0012 opt_send_without_block <callinfo!mid:m, argc:1, FCALL|KW_SPLAT>, <callcache> 0015 leave ``` After (basically the same except for the addition of swap): ``` == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,12)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 newhash 0 0003 putspecialobject 1 0005 swap 0006 putself 0007 opt_send_without_block <callinfo!mid:h, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0010 opt_send_without_block <callinfo!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE>, <callcache> 0013 opt_send_without_block <callinfo!mid:m, argc:1, FCALL|KW_SPLAT>, <callcache> 0016 leave ```
This commit is contained in:
parent
433c9c00d9
commit
1d5066efb0
Notes:
git
2019-09-06 01:58:16 +09:00
3 changed files with 41 additions and 11 deletions
30
compile.c
30
compile.c
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
10
parse.y
10
parse.y
|
@ -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);
|
||||
/*% %*/
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue