summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Zhu <peter@peterzhu.ca>2024-01-15 10:28:39 -0500
committerPeter Zhu <peter@peterzhu.ca>2024-01-15 17:12:53 -0500
commit6a175902f4fe31cf7617406e17d11deb9fccec32 (patch)
treea82d9d7de955215281b74c53427873428629ec6d
parentcc7b19e048aae4fb6ee75edf8498008042c42ee1 (diff)
[PRISM] Fix keyword splat inside of array
Fixes ruby/prism#2155.
-rw-r--r--prism_compile.c94
-rw-r--r--test/ruby/test_compile_prism.rb3
2 files changed, 95 insertions, 2 deletions
diff --git a/prism_compile.c b/prism_compile.c
index 79a122059f..38fbbb443a 100644
--- a/prism_compile.c
+++ b/prism_compile.c
@@ -3097,12 +3097,102 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
PM_POP_IF_POPPED;
}
else {
+ bool has_kw_splat = false;
+
for (size_t index = 0; index < elements->size; index++) {
- PM_COMPILE(elements->nodes[index]);
+ pm_node_t *element_node = elements->nodes[index];
+
+ switch (PM_NODE_TYPE(element_node)) {
+ case PM_KEYWORD_HASH_NODE: {
+ has_kw_splat = true;
+ pm_keyword_hash_node_t *keyword_arg = (pm_keyword_hash_node_t *)element_node;
+ size_t len = keyword_arg->elements.size;
+
+ int cur_hash_size = 0;
+
+ bool new_hash_emitted = false;
+ for (size_t i = 0; i < len; i++) {
+ pm_node_t *cur_node = keyword_arg->elements.nodes[i];
+
+ pm_node_type_t cur_type = PM_NODE_TYPE(cur_node);
+
+ switch (PM_NODE_TYPE(cur_node)) {
+ case PM_ASSOC_NODE: {
+ pm_assoc_node_t *assoc = (pm_assoc_node_t *)cur_node;
+
+ PM_COMPILE(assoc->key);
+ PM_COMPILE(assoc->value);
+ cur_hash_size++;
+
+ // If we're at the last keyword arg, or the last assoc node of this "set",
+ // then we want to either construct a newhash or merge onto previous hashes
+ if (i == (len - 1) || !PM_NODE_TYPE_P(keyword_arg->elements.nodes[i + 1], cur_type)) {
+ if (new_hash_emitted) {
+ ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_ptr, INT2FIX(cur_hash_size * 2 + 1));
+ }
+ else {
+ if (!popped) {
+ ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(cur_hash_size * 2));
+ cur_hash_size = 0;
+ new_hash_emitted = true;
+ }
+ }
+ }
+
+ break;
+ }
+ case PM_ASSOC_SPLAT_NODE: {
+ if (len > 1) {
+ ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
+ if (i == 0) {
+ if (!popped) {
+ ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(0));
+ new_hash_emitted = true;
+ }
+ }
+ else {
+ PM_SWAP;
+ }
+ }
+
+ pm_assoc_splat_node_t *assoc_splat = (pm_assoc_splat_node_t *)cur_node;
+ PM_COMPILE(assoc_splat->value);
+
+ if (len > 1) {
+ ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_kwd, INT2FIX(2));
+ }
+
+ if ((i < len - 1) && !PM_NODE_TYPE_P(keyword_arg->elements.nodes[i + 1], cur_type)) {
+ ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
+ PM_SWAP;
+ }
+
+ cur_hash_size = 0;
+ break;
+ }
+ default: {
+ rb_bug("Unknown type in keyword argument %s\n", pm_node_type_to_str(PM_NODE_TYPE(cur_node)));
+ }
+ }
+ }
+
+ break;
+ }
+ default: {
+ PM_COMPILE(element_node);
+
+ break;
+ }
+ }
}
if (!popped) {
- ADD_INSN1(ret, &dummy_line_node, newarray, INT2FIX(elements->size));
+ if (has_kw_splat) {
+ ADD_INSN1(ret, &dummy_line_node, newarraykwsplat, INT2FIX(elements->size));
+ }
+ else {
+ ADD_INSN1(ret, &dummy_line_node, newarray, INT2FIX(elements->size));
+ }
}
}
}
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb
index ca76a69acc..b6ff666fb9 100644
--- a/test/ruby/test_compile_prism.rb
+++ b/test/ruby/test_compile_prism.rb
@@ -702,6 +702,9 @@ module Prism
assert_prism_eval("[-1, true, 0, *1..2, 3, 4, *5..6, 7, 8, *9..11]")
assert_prism_eval("a = [1,2]; [0, *a, 3, 4, *5..6, 7, 8, *9..11]")
assert_prism_eval("[[*1..2], 3, *4..5]")
+
+ # Test keyword splat inside of array
+ assert_prism_eval("[**{x: 'hello'}]")
end
def test_AssocNode