Repository: ruby/prism
Pull request #1720 (2023-10-23 14:55:51)

Fix parsing ** within hash patterns
Previously `kwrest` was never used and ** got dumped into the regular assoc list when it was the first element in the list.


Patch:
diff --git a/config.yml b/config.yml
index 2f187d32..2e605ae5 100644
--- a/config.yml
+++ b/config.yml
@@ -1356,9 +1356,9 @@ nodes:
     fields:
       - name: constant
         type: node?
-      - name: assocs
+      - name: elements
         type: node[]
-      - name: kwrest
+      - name: rest
         type: node?
       - name: opening_loc
         type: location?
diff --git a/lib/prism/pattern.rb b/lib/prism/pattern.rb
index 0f606bc3..01f5b0ef 100644
--- a/lib/prism/pattern.rb
+++ b/lib/prism/pattern.rb
@@ -158,12 +158,12 @@ module Prism
     # in InstanceVariableReadNode[name: Symbol]
     # in { name: Symbol }
     def compile_hash_pattern_node(node)
-      compile_error(node) unless node.kwrest.nil?
+      compile_error(node) if node.rest
       compiled_constant = compile_node(node.constant) if node.constant
 
       preprocessed =
-        node.assocs.to_h do |assoc|
-          [assoc.key.unescaped.to_sym, compile_node(assoc.value)]
+        node.elements.to_h do |element|
+          [element.key.unescaped.to_sym, compile_node(element.value)]
         end
 
       compiled_keywords = ->(other) do
diff --git a/src/prism.c b/src/prism.c
index 9f70a1e2..17d6fb62 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -2588,10 +2588,10 @@ pm_hash_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *opening
             },
         },
         .constant = NULL,
-        .kwrest = NULL,
         .opening_loc = PM_LOCATION_TOKEN_VALUE(opening),
         .closing_loc = PM_LOCATION_TOKEN_VALUE(closing),
-        .assocs = PM_EMPTY_NODE_LIST
+        .elements = PM_EMPTY_NODE_LIST,
+        .rest = NULL
     };
 
     return node;
@@ -2599,27 +2599,44 @@ pm_hash_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *opening
 
 // Allocate and initialize a new hash pattern node.
 static pm_hash_pattern_node_t *
-pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *assocs) {
+pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *elements, pm_node_t *rest) {
     pm_hash_pattern_node_t *node = PM_ALLOC_NODE(parser, pm_hash_pattern_node_t);
 
+    const uint8_t *start;
+    const uint8_t *end;
+
+    if (elements->size > 0) {
+        if (rest) {
+            start = elements->nodes[0]->location.start;
+            end = rest->location.end;
+        } else {
+            start = elements->nodes[0]->location.start;
+            end = elements->nodes[elements->size - 1]->location.end;
+        }
+    } else {
+        assert(rest != NULL);
+        start = rest->location.start;
+        end = rest->location.end;
+    }
+
     *node = (pm_hash_pattern_node_t) {
         {
             .type = PM_HASH_PATTERN_NODE,
             .location = {
-                .start = assocs->nodes[0]->location.start,
-                .end = assocs->nodes[assocs->size - 1]->location.end
+                .start = start,
+                .end = end
             },
         },
         .constant = NULL,
-        .kwrest = NULL,
-        .assocs = PM_EMPTY_NODE_LIST,
+        .elements = PM_EMPTY_NODE_LIST,
+        .rest = rest,
         .opening_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE,
         .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE
     };
 
-    for (size_t index = 0; index < assocs->size; index++) {
-        pm_node_t *assoc = assocs->nodes[index];
-        pm_node_list_append(&node->assocs, assoc);
+    for (size_t index = 0; index < elements->size; index++) {
+        pm_node_t *element = elements->nodes[index];
+        pm_node_list_append(&node->elements, element);
     }
 
     return node;
@@ -11712,28 +11729,40 @@ parse_pattern_keyword_rest(pm_parser_t *parser) {
 // Parse a hash pattern.
 static pm_hash_pattern_node_t *
 parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_assoc) {
-    if (PM_NODE_TYPE_P(first_assoc, PM_ASSOC_NODE)) {
-        if (!match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) {
-            // Here we have a value for the first assoc in the list, so we will parse it
-            // now and update the first assoc.
-            pm_node_t *value = parse_pattern(parser, false, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY);
-
-            pm_assoc_node_t *assoc = (pm_assoc_node_t *) first_assoc;
-            assoc->base.location.end = value->location.end;
-            assoc->value = value;
-        } else {
-            pm_node_t *key = ((pm_assoc_node_t *) first_assoc)->key;
+    pm_node_list_t assocs = PM_EMPTY_NODE_LIST;
+    pm_node_t *rest = NULL;
 
-            if (PM_NODE_TYPE_P(key, PM_SYMBOL_NODE)) {
-                const pm_location_t *value_loc = &((pm_symbol_node_t *) key)->value_loc;
-                pm_parser_local_add_location(parser, value_loc->start, value_loc->end);
+    switch (PM_NODE_TYPE(first_assoc)) {
+        case PM_ASSOC_NODE: {
+            if (!match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) {
+                // Here we have a value for the first assoc in the list, so we will
+                // parse it now and update the first assoc.
+                pm_node_t *value = parse_pattern(parser, false, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY);
+
+                pm_assoc_node_t *assoc = (pm_assoc_node_t *) first_assoc;
+                assoc->base.location.end = value->location.end;
+                assoc->value = value;
+            } else {
+                pm_node_t *key = ((pm_assoc_node_t *) first_assoc)->key;
+
+                if (PM_NODE_TYPE_P(key, PM_SYMBOL_NODE)) {
+                    const pm_location_t *value_loc = &((pm_symbol_node_t *) key)->value_loc;
+                    pm_parser_local_add_location(parser, value_loc->start, value_loc->end);
+                }
             }
+
+            pm_node_list_append(&assocs, first_assoc);
+            break;
         }
+        case PM_ASSOC_SPLAT_NODE:
+        case PM_NO_KEYWORDS_PARAMETER_NODE:
+            rest = first_assoc;
+            break;
+        default:
+            assert(false);
+            break;
     }
 
-    pm_node_list_t assocs = PM_EMPTY_NODE_LIST;
-    pm_node_list_append(&assocs, first_assoc);
-
     // If there are any other assocs, then we'll parse them now.
     while (accept1(parser, PM_TOKEN_COMMA)) {
         // Here we need to break to support trailing commas.
@@ -11764,7 +11793,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_assoc) {
         pm_node_list_append(&assocs, assoc);
     }
 
-    pm_hash_pattern_node_t *node = pm_hash_pattern_node_node_list_create(parser, &assocs);
+    pm_hash_pattern_node_t *node = pm_hash_pattern_node_node_list_create(parser, &assocs, rest);
     free(assocs.nodes);
 
     return node;
@@ -11849,32 +11878,45 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) {
                 // pattern node.
                 node = pm_hash_pattern_node_empty_create(parser, &opening, &parser->previous);
             } else {
-                pm_node_t *key;
+                pm_node_t *first_assoc;
 
                 switch (parser->current.type) {
-                    case PM_TOKEN_LABEL:
+                    case PM_TOKEN_LABEL: {
                         parser_lex(parser);
-                        key = (pm_node_t *) pm_symbol_node_label_create(parser, &parser->previous);
+
+                        pm_symbol_node_t *key = pm_symbol_node_label_create(parser, &parser->previous);
+                        pm_token_t operator = not_provided(parser);
+
+                        first_assoc = (pm_node_t *) pm_assoc_node_create(parser, (pm_node_t *) key, &operator, NULL);
                         break;
+                    }
                     case PM_TOKEN_USTAR_STAR:
-                        key = parse_pattern_keyword_rest(parser);
+                        first_assoc = parse_pattern_keyword_rest(parser);
                         break;
-                    case PM_TOKEN_STRING_BEGIN:
-                        key = parse_expression(parser, PM_BINDING_POWER_MAX, PM_ERR_PATTERN_HASH_KEY);
+                    case PM_TOKEN_STRING_BEGIN: {
+                        pm_node_t *key = parse_expression(parser, PM_BINDING_POWER_MAX, PM_ERR_PATTERN_HASH_KEY);
+                        pm_token_t operator = not_provided(parser);
+
                         if (!pm_symbol_node_label_p(key)) {
                             pm_parser_err_node(parser, key, PM_ERR_PATTERN_HASH_KEY_LABEL);
                         }
 
+                        first_assoc = (pm_node_t *) pm_assoc_node_create(parser, key, &operator, NULL);
                         break;
-                    default:
+                    }
+                    default: {
                         parser_lex(parser);
                         pm_parser_err_previous(parser, PM_ERR_PATTERN_HASH_KEY);
-                        key = (pm_node_t *) pm_missing_node_create(parser, parser->previous.start, parser->previous.end);
+
+                        pm_missing_node_t *key = pm_missing_node_create(parser, parser->previous.start, parser->previous.end);
+                        pm_token_t operator = not_provided(parser);
+
+                        first_assoc = (pm_node_t *) pm_assoc_node_create(parser, (pm_node_t *) key, &operator, NULL);
                         break;
+                    }
                 }
 
-                pm_token_t operator = not_provided(parser);
-                node = parse_pattern_hash(parser, (pm_node_t *) pm_assoc_node_create(parser, key, &operator, NULL));
+                node = parse_pattern_hash(parser, first_assoc);
 
                 accept1(parser, PM_TOKEN_NEWLINE);
                 expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_PATTERN_TERM_BRACE);


Test patch:
diff --git a/test/prism/snapshots/patterns.txt b/test/prism/snapshots/patterns.txt
index 08a44780..0994330e 100644
--- a/test/prism/snapshots/patterns.txt
+++ b/test/prism/snapshots/patterns.txt
@@ -4466,7 +4466,7 @@
             │   ├── constant:
             │   │   @ ConstantReadNode (location: (188,7)-(188,8))
             │   │   └── name: :A
-            │   ├── assocs: (length: 1)
+            │   ├── elements: (length: 1)
             │   │   └── @ AssocNode (location: (189,2)-(191,3))
             │   │       ├── key:
             │   │       │   @ SymbolNode (location: (189,2)-(189,6))
@@ -4479,7 +4479,7 @@
             │   │       │   ├── constant:
             │   │       │   │   @ ConstantReadNode (location: (189,7)-(189,8))
             │   │       │   │   └── name: :B
-            │   │       │   ├── assocs: (length: 1)
+            │   │       │   ├── elements: (length: 1)
             │   │       │   │   └── @ AssocNode (location: (190,4)-(190,12))
             │   │       │   │       ├── key:
             │   │       │   │       │   @ SymbolNode (location: (190,4)-(190,10))
@@ -4492,11 +4492,11 @@
             │   │       │   │       │   ├── name: :a
             │   │       │   │       │   └── depth: 0
             │   │       │   │       └── operator_loc: ∅
-            │   │       │   ├── kwrest: ∅
+            │   │       │   ├── rest: ∅
             │   │       │   ├── opening_loc: (189,8)-(189,9) = "["
             │   │       │   └── closing_loc: (191,2)-(191,3) = "]"
             │   │       └── operator_loc: ∅
-            │   ├── kwrest: ∅
+            │   ├── rest: ∅
             │   ├── opening_loc: (188,8)-(188,9) = "["
             │   └── closing_loc: (192,0)-(192,1) = "]"
             └── operator_loc: (188,4)-(188,6) = "in"
diff --git a/test/prism/snapshots/seattlerb/case_in.txt b/test/prism/snapshots/seattlerb/case_in.txt
index 8830f80b..c6150c41 100644
--- a/test/prism/snapshots/seattlerb/case_in.txt
+++ b/test/prism/snapshots/seattlerb/case_in.txt
@@ -15,7 +15,7 @@
         │   │       ├── pattern:
         │   │       │   @ HashPatternNode (location: (2,4)-(2,8))
         │   │       │   ├── constant: ∅
-        │   │       │   ├── assocs: (length: 1)
+        │   │       │   ├── elements: (length: 1)
         │   │       │   │   └── @ AssocNode (location: (2,4)-(2,8))
         │   │       │   │       ├── key:
         │   │       │   │       │   @ SymbolNode (location: (2,4)-(2,8))
@@ -25,7 +25,7 @@
         │   │       │   │       │   └── unescaped: "b"
         │   │       │   │       ├── value: ∅
         │   │       │   │       └── operator_loc: ∅
-        │   │       │   ├── kwrest: ∅
+        │   │       │   ├── rest: ∅
         │   │       │   ├── opening_loc: ∅
         │   │       │   └── closing_loc: ∅
         │   │       ├── statements: ∅
@@ -282,11 +282,11 @@
         │   │       ├── pattern:
         │   │       │   @ HashPatternNode (location: (42,3)-(42,8))
         │   │       │   ├── constant: ∅
-        │   │       │   ├── assocs: (length: 1)
-        │   │       │   │   └── @ NoKeywordsParameterNode (location: (42,3)-(42,8))
-        │   │       │   │       ├── operator_loc: (42,3)-(42,5) = "**"
-        │   │       │   │       └── keyword_loc: (42,5)-(42,8) = "nil"
-        │   │       │   ├── kwrest: ∅
+        │   │       │   ├── elements: (length: 0)
+        │   │       │   ├── rest:
+        │   │       │   │   @ NoKeywordsParameterNode (location: (42,3)-(42,8))
+        │   │       │   │   ├── operator_loc: (42,3)-(42,5) = "**"
+        │   │       │   │   └── keyword_loc: (42,5)-(42,8) = "nil"
         │   │       │   ├── opening_loc: ∅
         │   │       │   └── closing_loc: ∅
         │   │       ├── statements: ∅
@@ -849,7 +849,7 @@
         │   │       ├── pattern:
         │   │       │   @ HashPatternNode (location: (106,3)-(106,11))
         │   │       │   ├── constant: ∅
-        │   │       │   ├── assocs: (length: 1)
+        │   │       │   ├── elements: (length: 1)
         │   │       │   │   └── @ AssocNode (location: (106,5)-(106,9))
         │   │       │   │       ├── key:
         │   │       │   │       │   @ SymbolNode (location: (106,5)-(106,9))
@@ -859,7 +859,7 @@
         │   │       │   │       │   └── unescaped: "b"
         │   │       │   │       ├── value: ∅
         │   │       │   │       └── operator_loc: ∅
-        │   │       │   ├── kwrest: ∅
+        │   │       │   ├── rest: ∅
         │   │       │   ├── opening_loc: (106,3)-(106,4) = "{"
         │   │       │   └── closing_loc: (106,10)-(106,11) = "}"
         │   │       ├── statements: ∅
@@ -880,8 +880,8 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (110,3)-(110,5))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 0)
-            │       │   ├── kwrest: ∅
+            │       │   ├── elements: (length: 0)
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: (110,3)-(110,4) = "{"
             │       │   └── closing_loc: (110,4)-(110,5) = "}"
             │       ├── statements: ∅
diff --git a/test/prism/snapshots/seattlerb/case_in_37.txt b/test/prism/snapshots/seattlerb/case_in_37.txt
index 46bdcf40..b272291f 100644
--- a/test/prism/snapshots/seattlerb/case_in_37.txt
+++ b/test/prism/snapshots/seattlerb/case_in_37.txt
@@ -15,7 +15,7 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,19))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 1)
+            │       │   ├── elements: (length: 1)
             │       │   │   └── @ AssocNode (location: (2,5)-(2,17))
             │       │   │       ├── key:
             │       │   │       │   @ SymbolNode (location: (2,5)-(2,7))
@@ -37,7 +37,7 @@
             │       │   │       │   ├── opening_loc: (2,8)-(2,9) = "["
             │       │   │       │   └── closing_loc: (2,16)-(2,17) = "]"
             │       │   │       └── operator_loc: ∅
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: (2,3)-(2,4) = "{"
             │       │   └── closing_loc: (2,18)-(2,19) = "}"
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/case_in_hash_pat.txt b/test/prism/snapshots/seattlerb/case_in_hash_pat.txt
index e0391402..a8e27b47 100644
--- a/test/prism/snapshots/seattlerb/case_in_hash_pat.txt
+++ b/test/prism/snapshots/seattlerb/case_in_hash_pat.txt
@@ -15,7 +15,7 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,21))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 2)
+            │       │   ├── elements: (length: 2)
             │       │   │   ├── @ AssocNode (location: (2,5)-(2,11))
             │       │   │   │   ├── key:
             │       │   │   │   │   @ SymbolNode (location: (2,5)-(2,7))
@@ -46,7 +46,7 @@
             │       │   │       │   ├── closing_loc: (2,18)-(2,19) = "\""
             │       │   │       │   └── unescaped: "e"
             │       │   │       └── operator_loc: ∅
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: (2,3)-(2,4) = "{"
             │       │   └── closing_loc: (2,20)-(2,21) = "}"
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/case_in_hash_pat_assign.txt b/test/prism/snapshots/seattlerb/case_in_hash_pat_assign.txt
index b24718b1..4df1e565 100644
--- a/test/prism/snapshots/seattlerb/case_in_hash_pat_assign.txt
+++ b/test/prism/snapshots/seattlerb/case_in_hash_pat_assign.txt
@@ -15,7 +15,7 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,34))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 3)
+            │       │   ├── elements: (length: 3)
             │       │   │   ├── @ AssocNode (location: (2,5)-(2,20))
             │       │   │   │   ├── key:
             │       │   │   │   │   @ SymbolNode (location: (2,5)-(2,7))
@@ -58,7 +58,7 @@
             │       │   │       │   └── unescaped: "f"
             │       │   │       ├── value: ∅
             │       │   │       └── operator_loc: ∅
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: (2,3)-(2,4) = "{"
             │       │   └── closing_loc: (2,33)-(2,34) = "}"
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/case_in_hash_pat_paren_assign.txt b/test/prism/snapshots/seattlerb/case_in_hash_pat_paren_assign.txt
index 802c0943..a444e523 100644
--- a/test/prism/snapshots/seattlerb/case_in_hash_pat_paren_assign.txt
+++ b/test/prism/snapshots/seattlerb/case_in_hash_pat_paren_assign.txt
@@ -17,7 +17,7 @@
             │       │   ├── constant:
             │       │   │   @ ConstantReadNode (location: (2,3)-(2,4))
             │       │   │   └── name: :B
-            │       │   ├── assocs: (length: 1)
+            │       │   ├── elements: (length: 1)
             │       │   │   └── @ AssocNode (location: (2,5)-(2,10))
             │       │   │       ├── key:
             │       │   │       │   @ SymbolNode (location: (2,5)-(2,7))
@@ -29,7 +29,7 @@
             │       │   │       │   @ IntegerNode (location: (2,8)-(2,10))
             │       │   │       │   └── flags: decimal
             │       │   │       └── operator_loc: ∅
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: (2,4)-(2,5) = "("
             │       │   └── closing_loc: (2,10)-(2,11) = ")"
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/case_in_hash_pat_paren_true.txt b/test/prism/snapshots/seattlerb/case_in_hash_pat_paren_true.txt
index ca7fbcfd..05a111f0 100644
--- a/test/prism/snapshots/seattlerb/case_in_hash_pat_paren_true.txt
+++ b/test/prism/snapshots/seattlerb/case_in_hash_pat_paren_true.txt
@@ -15,7 +15,7 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,10))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 1)
+            │       │   ├── elements: (length: 1)
             │       │   │   └── @ AssocNode (location: (2,3)-(2,10))
             │       │   │       ├── key:
             │       │   │       │   @ SymbolNode (location: (2,3)-(2,5))
@@ -26,7 +26,7 @@
             │       │   │       ├── value:
             │       │   │       │   @ TrueNode (location: (2,6)-(2,10))
             │       │   │       └── operator_loc: ∅
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: ∅
             │       │   └── closing_loc: ∅
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/case_in_hash_pat_rest.txt b/test/prism/snapshots/seattlerb/case_in_hash_pat_rest.txt
index bbd49630..90e32521 100644
--- a/test/prism/snapshots/seattlerb/case_in_hash_pat_rest.txt
+++ b/test/prism/snapshots/seattlerb/case_in_hash_pat_rest.txt
@@ -15,7 +15,7 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,15))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 2)
+            │       │   ├── elements: (length: 2)
             │       │   │   ├── @ AssocNode (location: (2,3)-(2,7))
             │       │   │   │   ├── key:
             │       │   │   │   │   @ SymbolNode (location: (2,3)-(2,5))
@@ -34,7 +34,7 @@
             │       │   │       │   ├── name: :rest
             │       │   │       │   └── depth: 0
             │       │   │       └── operator_loc: (2,9)-(2,11) = "**"
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: ∅
             │       │   └── closing_loc: ∅
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/case_in_hash_pat_rest_solo.txt b/test/prism/snapshots/seattlerb/case_in_hash_pat_rest_solo.txt
index 4a189bb5..68503300 100644
--- a/test/prism/snapshots/seattlerb/case_in_hash_pat_rest_solo.txt
+++ b/test/prism/snapshots/seattlerb/case_in_hash_pat_rest_solo.txt
@@ -15,14 +15,14 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,9))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 1)
-            │       │   │   └── @ AssocSplatNode (location: (2,3)-(2,9))
-            │       │   │       ├── value:
-            │       │   │       │   @ LocalVariableTargetNode (location: (2,5)-(2,9))
-            │       │   │       │   ├── name: :rest
-            │       │   │       │   └── depth: 0
-            │       │   │       └── operator_loc: (2,3)-(2,5) = "**"
-            │       │   ├── kwrest: ∅
+            │       │   ├── elements: (length: 0)
+            │       │   ├── rest:
+            │       │   │   @ AssocSplatNode (location: (2,3)-(2,9))
+            │       │   │   ├── value:
+            │       │   │   │   @ LocalVariableTargetNode (location: (2,5)-(2,9))
+            │       │   │   │   ├── name: :rest
+            │       │   │   │   └── depth: 0
+            │       │   │   └── operator_loc: (2,3)-(2,5) = "**"
             │       │   ├── opening_loc: ∅
             │       │   └── closing_loc: ∅
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/parse_pattern_058.txt b/test/prism/snapshots/seattlerb/parse_pattern_058.txt
index 15010ca2..4812556b 100644
--- a/test/prism/snapshots/seattlerb/parse_pattern_058.txt
+++ b/test/prism/snapshots/seattlerb/parse_pattern_058.txt
@@ -25,7 +25,7 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,15))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 2)
+            │       │   ├── elements: (length: 2)
             │       │   │   ├── @ AssocNode (location: (2,4)-(2,6))
             │       │   │   │   ├── key:
             │       │   │   │   │   @ SymbolNode (location: (2,4)-(2,6))
@@ -41,7 +41,7 @@
             │       │   │       │   ├── name: :rest
             │       │   │       │   └── depth: 0
             │       │   │       └── operator_loc: (2,8)-(2,10) = "**"
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: (2,3)-(2,4) = "{"
             │       │   └── closing_loc: (2,14)-(2,15) = "}"
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/parse_pattern_058_2.txt b/test/prism/snapshots/seattlerb/parse_pattern_058_2.txt
index dc89fc36..4e5bf2f2 100644
--- a/test/prism/snapshots/seattlerb/parse_pattern_058_2.txt
+++ b/test/prism/snapshots/seattlerb/parse_pattern_058_2.txt
@@ -25,7 +25,7 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,11))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 2)
+            │       │   ├── elements: (length: 2)
             │       │   │   ├── @ AssocNode (location: (2,4)-(2,6))
             │       │   │   │   ├── key:
             │       │   │   │   │   @ SymbolNode (location: (2,4)-(2,6))
@@ -38,7 +38,7 @@
             │       │   │   └── @ AssocSplatNode (location: (2,8)-(2,10))
             │       │   │       ├── value: ∅
             │       │   │       └── operator_loc: (2,8)-(2,10) = "**"
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: (2,3)-(2,4) = "{"
             │       │   └── closing_loc: (2,10)-(2,11) = "}"
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/parse_pattern_069.txt b/test/prism/snapshots/seattlerb/parse_pattern_069.txt
index 4d1e51e3..ae40e65d 100644
--- a/test/prism/snapshots/seattlerb/parse_pattern_069.txt
+++ b/test/prism/snapshots/seattlerb/parse_pattern_069.txt
@@ -17,7 +17,7 @@
             │       │   ├── constant:
             │       │   │   @ ConstantReadNode (location: (2,3)-(2,9))
             │       │   │   └── name: :Object
-            │       │   ├── assocs: (length: 1)
+            │       │   ├── elements: (length: 1)
             │       │   │   └── @ AssocNode (location: (2,10)-(2,14))
             │       │   │       ├── key:
             │       │   │       │   @ SymbolNode (location: (2,10)-(2,12))
@@ -29,7 +29,7 @@
             │       │   │       │   @ IntegerNode (location: (2,13)-(2,14))
             │       │   │       │   └── flags: decimal
             │       │   │       └── operator_loc: ∅
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: (2,9)-(2,10) = "["
             │       │   └── closing_loc: (2,14)-(2,15) = "]"
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/parse_pattern_076.txt b/test/prism/snapshots/seattlerb/parse_pattern_076.txt
index bce1fdce..b6643f86 100644
--- a/test/prism/snapshots/seattlerb/parse_pattern_076.txt
+++ b/test/prism/snapshots/seattlerb/parse_pattern_076.txt
@@ -25,7 +25,7 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,16))
             │       │   ├── constant: ∅
-            │       │   ├── assocs: (length: 2)
+            │       │   ├── elements: (length: 2)
             │       │   │   ├── @ AssocNode (location: (2,4)-(2,8))
             │       │   │   │   ├── key:
             │       │   │   │   │   @ SymbolNode (location: (2,4)-(2,6))
@@ -40,7 +40,7 @@
             │       │   │   └── @ NoKeywordsParameterNode (location: (2,10)-(2,15))
             │       │   │       ├── operator_loc: (2,10)-(2,12) = "**"
             │       │   │       └── keyword_loc: (2,12)-(2,15) = "nil"
-            │       │   ├── kwrest: ∅
+            │       │   ├── rest: ∅
             │       │   ├── opening_loc: (2,3)-(2,4) = "{"
             │       │   └── closing_loc: (2,15)-(2,16) = "}"
             │       ├── statements:
diff --git a/test/prism/snapshots/unparser/corpus/literal/pattern.txt b/test/prism/snapshots/unparser/corpus/literal/pattern.txt
index cf0132df..f687b728 100644
--- a/test/prism/snapshots/unparser/corpus/literal/pattern.txt
+++ b/test/prism/snapshots/unparser/corpus/literal/pattern.txt
@@ -79,7 +79,7 @@
         │   │   │   │   ├── constant:
         │   │   │   │   │   @ ConstantReadNode (location: (6,3)-(6,4))
         │   │   │   │   │   └── name: :A
-        │   │   │   │   ├── assocs: (length: 1)
+        │   │   │   │   ├── elements: (length: 1)
         │   │   │   │   │   └── @ AssocNode (location: (6,5)-(6,7))
         │   │   │   │   │       ├── key:
         │   │   │   │   │       │   @ SymbolNode (location: (6,5)-(6,7))
@@ -89,7 +89,7 @@
         │   │   │   │   │       │   └── unescaped: "x"
         │   │   │   │   │       ├── value: ∅
         │   │   │   │   │       └── operator_loc: ∅
-        │   │   │   │   ├── kwrest: ∅
+        │   │   │   │   ├── rest: ∅
         │   │   │   │   ├── opening_loc: (6,4)-(6,5) = "("
         │   │   │   │   └── closing_loc: (6,7)-(6,8) = ")"
         │   │   │   ├── statements:
@@ -102,18 +102,14 @@
         │   │   │   ├── pattern:
         │   │   │   │   @ HashPatternNode (location: (8,3)-(8,8))
         │   │   │   │   ├── constant: ∅
-        │   │   │   │   ├── assocs: (length: 1)
-        │   │   │   │   │   └── @ AssocNode (location: (8,4)-(8,7))
-        │   │   │   │   │       ├── key:
-        │   │   │   │   │       │   @ AssocSplatNode (location: (8,4)-(8,7))
-        │   │   │   │   │       │   ├── value:
-        │   │   │   │   │       │   │   @ LocalVariableTargetNode (location: (8,6)-(8,7))
-        │   │   │   │   │       │   │   ├── name: :a
-        │   │   │   │   │       │   │   └── depth: 0
-        │   │   │   │   │       │   └── operator_loc: (8,4)-(8,6) = "**"
-        │   │   │   │   │       ├── value: ∅
-        │   │   │   │   │       └── operator_loc: ∅
-        │   │   │   │   ├── kwrest: ∅
+        │   │   │   │   ├── elements: (length: 0)
+        │   │   │   │   ├── rest:
+        │   │   │   │   │   @ AssocSplatNode (location: (8,4)-(8,7))
+        │   │   │   │   │   ├── value:
+        │   │   │   │   │   │   @ LocalVariableTargetNode (location: (8,6)-(8,7))
+        │   │   │   │   │   │   ├── name: :a
+        │   │   │   │   │   │   └── depth: 0
+        │   │   │   │   │   └── operator_loc: (8,4)-(8,6) = "**"
         │   │   │   │   ├── opening_loc: (8,3)-(8,4) = "{"
         │   │   │   │   └── closing_loc: (8,7)-(8,8) = "}"
         │   │   │   ├── statements:
@@ -133,8 +129,8 @@
         │   │   │   │   │   └── body: (length: 1)
         │   │   │   │   │       └── @ HashPatternNode (location: (10,3)-(10,5))
         │   │   │   │   │           ├── constant: ∅
-        │   │   │   │   │           ├── assocs: (length: 0)
-        │   │   │   │   │           ├── kwrest: ∅
+        │   │   │   │   │           ├── elements: (length: 0)
+        │   │   │   │   │           ├── rest: ∅
         │   │   │   │   │           ├── opening_loc: (10,3)-(10,4) = "{"
         │   │   │   │   │           └── closing_loc: (10,4)-(10,5) = "}"
         │   │   │   │   ├── consequent: ∅
@@ -173,7 +169,7 @@
         │   │   │   ├── pattern:
         │   │   │   │   @ HashPatternNode (location: (14,3)-(14,16))
         │   │   │   │   ├── constant: ∅
-        │   │   │   │   ├── assocs: (length: 2)
+        │   │   │   │   ├── elements: (length: 2)
         │   │   │   │   │   ├── @ AssocNode (location: (14,4)-(14,8))
         │   │   │   │   │   │   ├── key:
         │   │   │   │   │   │   │   @ SymbolNode (location: (14,4)-(14,6))
@@ -196,7 +192,7 @@
         │   │   │   │   │       │   @ IntegerNode (location: (14,14)-(14,15))
         │   │   │   │   │       │   └── flags: decimal
         │   │   │   │   │       └── operator_loc: ∅
-        │   │   │   │   ├── kwrest: ∅
+        │   │   │   │   ├── rest: ∅
         │   │   │   │   ├── opening_loc: (14,3)-(14,4) = "{"
         │   │   │   │   └── closing_loc: (14,15)-(14,16) = "}"
         │   │   │   ├── statements:
@@ -209,8 +205,8 @@
         │   │   │   ├── pattern:
         │   │   │   │   @ HashPatternNode (location: (16,3)-(16,5))
         │   │   │   │   ├── constant: ∅
-        │   │   │   │   ├── assocs: (length: 0)
-        │   │   │   │   ├── kwrest: ∅
+        │   │   │   │   ├── elements: (length: 0)
+        │   │   │   │   ├── rest: ∅
         │   │   │   │   ├── opening_loc: (16,3)-(16,4) = "{"
         │   │   │   │   └── closing_loc: (16,4)-(16,5) = "}"
         │   │   │   ├── statements:
@@ -223,15 +219,11 @@
         │   │   │   ├── pattern:
         │   │   │   │   @ HashPatternNode (location: (18,3)-(18,10))
         │   │   │   │   ├── constant: ∅
-        │   │   │   │   ├── assocs: (length: 1)
-        │   │   │   │   │   └── @ AssocNode (location: (18,4)-(18,9))
-        │   │   │   │   │       ├── key:
-        │   │   │   │   │       │   @ NoKeywordsParameterNode (location: (18,4)-(18,9))
-        │   │   │   │   │       │   ├── operator_loc: (18,4)-(18,6) = "**"
-        │   │   │   │   │       │   └── keyword_loc: (18,6)-(18,9) = "nil"
-        │   │   │   │   │       ├── value: ∅
-        │   │   │   │   │       └── operator_loc: ∅
-        │   │   │   │   ├── kwrest: ∅
+        │   │   │   │   ├── elements: (length: 0)
+        │   │   │   │   ├── rest:
+        │   │   │   │   │   @ NoKeywordsParameterNode (location: (18,4)-(18,9))
+        │   │   │   │   │   ├── operator_loc: (18,4)-(18,6) = "**"
+        │   │   │   │   │   └── keyword_loc: (18,6)-(18,9) = "nil"
         │   │   │   │   ├── opening_loc: (18,3)-(18,4) = "{"
         │   │   │   │   └── closing_loc: (18,9)-(18,10) = "}"
         │   │   │   ├── statements:
@@ -244,7 +236,7 @@
         │   │   │   ├── pattern:
         │   │   │   │   @ HashPatternNode (location: (20,3)-(20,11))
         │   │   │   │   ├── constant: ∅
-        │   │   │   │   ├── assocs: (length: 1)
+        │   │   │   │   ├── elements: (length: 1)
         │   │   │   │   │   └── @ AssocNode (location: (20,4)-(20,10))
         │   │   │   │   │       ├── key:
         │   │   │   │   │       │   @ SymbolNode (location: (20,4)-(20,8))
@@ -256,7 +248,7 @@
         │   │   │   │   │       │   @ IntegerNode (location: (20,9)-(20,10))
         │   │   │   │   │       │   └── flags: decimal
         │   │   │   │   │       └── operator_loc: ∅
-        │   │   │   │   ├── kwrest: ∅
+        │   │   │   │   ├── rest: ∅
         │   │   │   │   ├── opening_loc: (20,3)-(20,4) = "{"
         │   │   │   │   └── closing_loc: (20,10)-(20,11) = "}"
         │   │   │   ├── statements:
diff --git a/test/prism/snapshots/whitequark/multiple_pattern_matches.txt b/test/prism/snapshots/whitequark/multiple_pattern_matches.txt
index ee1cbd38..19a8eba1 100644
--- a/test/prism/snapshots/whitequark/multiple_pattern_matches.txt
+++ b/test/prism/snapshots/whitequark/multiple_pattern_matches.txt
@@ -23,7 +23,7 @@
         │   ├── pattern:
         │   │   @ HashPatternNode (location: (1,10)-(1,12))
         │   │   ├── constant: ∅
-        │   │   ├── assocs: (length: 1)
+        │   │   ├── elements: (length: 1)
         │   │   │   └── @ AssocNode (location: (1,10)-(1,12))
         │   │   │       ├── key:
         │   │   │       │   @ SymbolNode (location: (1,10)-(1,12))
@@ -33,7 +33,7 @@
         │   │   │       │   └── unescaped: "a"
         │   │   │       ├── value: ∅
         │   │   │       └── operator_loc: ∅
-        │   │   ├── kwrest: ∅
+        │   │   ├── rest: ∅
         │   │   ├── opening_loc: ∅
         │   │   └── closing_loc: ∅
         │   └── operator_loc: (1,7)-(1,9) = "=>"
@@ -57,7 +57,7 @@
         │   ├── pattern:
         │   │   @ HashPatternNode (location: (2,10)-(2,12))
         │   │   ├── constant: ∅
-        │   │   ├── assocs: (length: 1)
+        │   │   ├── elements: (length: 1)
         │   │   │   └── @ AssocNode (location: (2,10)-(2,12))
         │   │   │       ├── key:
         │   │   │       │   @ SymbolNode (location: (2,10)-(2,12))
@@ -67,7 +67,7 @@
         │   │   │       │   └── unescaped: "a"
         │   │   │       ├── value: ∅
         │   │   │       └── operator_loc: ∅
-        │   │   ├── kwrest: ∅
+        │   │   ├── rest: ∅
         │   │   ├── opening_loc: ∅
         │   │   └── closing_loc: ∅
         │   └── operator_loc: (2,7)-(2,9) = "=>"
@@ -91,7 +91,7 @@
         │   ├── pattern:
         │   │   @ HashPatternNode (location: (4,10)-(4,12))
         │   │   ├── constant: ∅
-        │   │   ├── assocs: (length: 1)
+        │   │   ├── elements: (length: 1)
         │   │   │   └── @ AssocNode (location: (4,10)-(4,12))
         │   │   │       ├── key:
         │   │   │       │   @ SymbolNode (location: (4,10)-(4,12))
@@ -101,7 +101,7 @@
         │   │   │       │   └── unescaped: "a"
         │   │   │       ├── value: ∅
         │   │   │       └── operator_loc: ∅
-        │   │   ├── kwrest: ∅
+        │   │   ├── rest: ∅
         │   │   ├── opening_loc: ∅
         │   │   └── closing_loc: ∅
         │   └── operator_loc: (4,7)-(4,9) = "in"
@@ -125,7 +125,7 @@
             ├── pattern:
             │   @ HashPatternNode (location: (5,10)-(5,12))
             │   ├── constant: ∅
-            │   ├── assocs: (length: 1)
+            │   ├── elements: (length: 1)
             │   │   └── @ AssocNode (location: (5,10)-(5,12))
             │   │       ├── key:
             │   │       │   @ SymbolNode (location: (5,10)-(5,12))
@@ -135,7 +135,7 @@
             │   │       │   └── unescaped: "a"
             │   │       ├── value: ∅
             │   │       └── operator_loc: ∅
-            │   ├── kwrest: ∅
+            │   ├── rest: ∅
             │   ├── opening_loc: ∅
             │   └── closing_loc: ∅
             └── operator_loc: (5,7)-(5,9) = "in"
diff --git a/test/prism/snapshots/whitequark/newline_in_hash_argument.txt b/test/prism/snapshots/whitequark/newline_in_hash_argument.txt
index 3c575f4a..9d29c284 100644
--- a/test/prism/snapshots/whitequark/newline_in_hash_argument.txt
+++ b/test/prism/snapshots/whitequark/newline_in_hash_argument.txt
@@ -20,7 +20,7 @@
         │   │   │   ├── pattern:
         │   │   │   │   @ HashPatternNode (location: (2,3)-(2,5))
         │   │   │   │   ├── constant: ∅
-        │   │   │   │   ├── assocs: (length: 1)
+        │   │   │   │   ├── elements: (length: 1)
         │   │   │   │   │   └── @ AssocNode (location: (2,3)-(2,5))
         │   │   │   │   │       ├── key:
         │   │   │   │   │       │   @ SymbolNode (location: (2,3)-(2,5))
@@ -30,7 +30,7 @@
         │   │   │   │   │       │   └── unescaped: "a"
         │   │   │   │   │       ├── value: ∅
         │   │   │   │   │       └── operator_loc: ∅
-        │   │   │   │   ├── kwrest: ∅
+        │   │   │   │   ├── rest: ∅
         │   │   │   │   ├── opening_loc: ∅
         │   │   │   │   └── closing_loc: ∅
         │   │   │   ├── statements:
@@ -45,7 +45,7 @@
         │   │       ├── pattern:
         │   │       │   @ HashPatternNode (location: (5,3)-(5,7))
         │   │       │   ├── constant: ∅
-        │   │       │   ├── assocs: (length: 1)
+        │   │       │   ├── elements: (length: 1)
         │   │       │   │   └── @ AssocNode (location: (5,3)-(5,7))
         │   │       │   │       ├── key:
         │   │       │   │       │   @ SymbolNode (location: (5,3)-(5,7))
@@ -55,7 +55,7 @@
         │   │       │   │       │   └── unescaped: "b"
         │   │       │   │       ├── value: ∅
         │   │       │   │       └── operator_loc: ∅
-        │   │       │   ├── kwrest: ∅
+        │   │       │   ├── rest: ∅
         │   │       │   ├── opening_loc: ∅
         │   │       │   └── closing_loc: ∅
         │   │       ├── statements:
diff --git a/test/prism/snapshots/whitequark/pattern_matching_single_line_allowed_omission_of_parentheses.txt b/test/prism/snapshots/whitequark/pattern_matching_single_line_allowed_omission_of_parentheses.txt
index faf1c957..b44bedc8 100644
--- a/test/prism/snapshots/whitequark/pattern_matching_single_line_allowed_omission_of_parentheses.txt
+++ b/test/prism/snapshots/whitequark/pattern_matching_single_line_allowed_omission_of_parentheses.txt
@@ -79,7 +79,7 @@
         │   ├── pattern:
         │   │   @ HashPatternNode (location: (5,10)-(5,12))
         │   │   ├── constant: ∅
-        │   │   ├── assocs: (length: 1)
+        │   │   ├── elements: (length: 1)
         │   │   │   └── @ AssocNode (location: (5,10)-(5,12))
         │   │   │       ├── key:
         │   │   │       │   @ SymbolNode (location: (5,10)-(5,12))
@@ -89,7 +89,7 @@
         │   │   │       │   └── unescaped: "a"
         │   │   │       ├── value: ∅
         │   │   │       └── operator_loc: ∅
-        │   │   ├── kwrest: ∅
+        │   │   ├── rest: ∅
         │   │   ├── opening_loc: ∅
         │   │   └── closing_loc: ∅
         │   └── operator_loc: (5,7)-(5,9) = "=>"
@@ -116,7 +116,7 @@
         │   ├── pattern:
         │   │   @ HashPatternNode (location: (7,10)-(7,12))
         │   │   ├── constant: ∅
-        │   │   ├── assocs: (length: 1)
+        │   │   ├── elements: (length: 1)
         │   │   │   └── @ AssocNode (location: (7,10)-(7,12))
         │   │   │       ├── key:
         │   │   │       │   @ SymbolNode (location: (7,10)-(7,12))
@@ -126,7 +126,7 @@
         │   │   │       │   └── unescaped: "a"
         │   │   │       ├── value: ∅
         │   │   │       └── operator_loc: ∅
-        │   │   ├── kwrest: ∅
+        │   │   ├── rest: ∅
         │   │   ├── opening_loc: ∅
         │   │   └── closing_loc: ∅
         │   └── operator_loc: (7,7)-(7,9) = "in"
@@ -156,7 +156,7 @@
         │   ├── pattern:
         │   │   @ HashPatternNode (location: (9,17)-(9,27))
         │   │   ├── constant: ∅
-        │   │   ├── assocs: (length: 1)
+        │   │   ├── elements: (length: 1)
         │   │   │   └── @ AssocNode (location: (9,17)-(9,27))
         │   │   │       ├── key:
         │   │   │       │   @ SymbolNode (location: (9,17)-(9,21))
@@ -169,7 +169,7 @@
         │   │   │       │   ├── name: :value
         │   │   │       │   └── depth: 0
         │   │   │       └── operator_loc: ∅
-        │   │   ├── kwrest: ∅
+        │   │   ├── rest: ∅
         │   │   ├── opening_loc: ∅
         │   │   └── closing_loc: ∅
         │   └── operator_loc: (9,14)-(9,16) = "=>"
@@ -199,7 +199,7 @@
         │   ├── pattern:
         │   │   @ HashPatternNode (location: (11,17)-(11,27))
         │   │   ├── constant: ∅
-        │   │   ├── assocs: (length: 1)
+        │   │   ├── elements: (length: 1)
         │   │   │   └── @ AssocNode (location: (11,17)-(11,27))
         │   │   │       ├── key:
         │   │   │       │   @ SymbolNode (location: (11,17)-(11,21))
@@ -212,7 +212,7 @@
         │   │   │       │   ├── name: :value
         │   │   │       │   └── depth: 0
         │   │   │       └── operator_loc: ∅
-        │   │   ├── kwrest: ∅
+        │   │   ├── rest: ∅
         │   │   ├── opening_loc: ∅
         │   │   └── closing_loc: ∅
         │   └── operator_loc: (11,14)-(11,16) = "in"