Repository: ruby/prism
Pull request #2070 (2023-12-14 02:36:17)

Fix hash pattern rest
In hash patterns, for some reason ** was showing up in elements and not on the `rest` field as originally intended. Also this properly rejects some things we should be, like:

```ruby
{} => { **a, b: 1 }
```


Patch:
diff --git a/include/prism/diagnostic.h b/include/prism/diagnostic.h
index a1e0b6d0..fc1f0c2b 100644
--- a/include/prism/diagnostic.h
+++ b/include/prism/diagnostic.h
@@ -211,6 +211,7 @@ typedef enum {
     PM_ERR_PATTERN_EXPRESSION_AFTER_PIN,
     PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE,
     PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE,
+    PM_ERR_PATTERN_EXPRESSION_AFTER_REST,
     PM_ERR_PATTERN_HASH_KEY,
     PM_ERR_PATTERN_HASH_KEY_LABEL,
     PM_ERR_PATTERN_IDENT_AFTER_HROCKET,
diff --git a/src/diagnostic.c b/src/diagnostic.c
index eaf6112d..1fc8c50c 100644
--- a/src/diagnostic.c
+++ b/src/diagnostic.c
@@ -219,6 +219,7 @@ static const char* const diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
     [PM_ERR_PATTERN_EXPRESSION_AFTER_PIN]       = "expected a pattern expression after the `^` pin operator",
     [PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE]      = "expected a pattern expression after the `|` operator",
     [PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE]     = "expected a pattern expression after the range operator",
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_REST]      = "unexpected pattern expression after the `**` expression",
     [PM_ERR_PATTERN_HASH_KEY]                   = "expected a key in the hash pattern",
     [PM_ERR_PATTERN_HASH_KEY_LABEL]             = "expected a label as the key in the hash pattern", // TODO // THIS // AND // ABOVE // IS WEIRD
     [PM_ERR_PATTERN_IDENT_AFTER_HROCKET]        = "expected an identifier after the `=>` operator",
diff --git a/src/prism.c b/src/prism.c
index 4b15f658..dfb1affa 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -13121,10 +13121,15 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_assoc) {
             break;
         }
 
-        pm_node_t *assoc;
-
         if (match1(parser, PM_TOKEN_USTAR_STAR)) {
-            assoc = parse_pattern_keyword_rest(parser);
+            pm_node_t *assoc = parse_pattern_keyword_rest(parser);
+
+            if (rest == NULL) {
+                rest = assoc;
+            } else {
+                pm_parser_err_node(parser, assoc, PM_ERR_PATTERN_EXPRESSION_AFTER_REST);
+                pm_node_list_append(&assocs, assoc);
+            }
         } else {
             expect1(parser, PM_TOKEN_LABEL, PM_ERR_PATTERN_LABEL_AFTER_COMMA);
             pm_node_t *key = (pm_node_t *) pm_symbol_node_label_create(parser, &parser->previous);
@@ -13138,10 +13143,14 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_assoc) {
             }
 
             pm_token_t operator = not_provided(parser);
-            assoc = (pm_node_t *) pm_assoc_node_create(parser, key, &operator, value);
-        }
+            pm_node_t *assoc = (pm_node_t *) pm_assoc_node_create(parser, key, &operator, value);
+
+            if (rest != NULL) {
+                pm_parser_err_node(parser, assoc, PM_ERR_PATTERN_EXPRESSION_AFTER_REST);
+            }
 
-        pm_node_list_append(&assocs, assoc);
+            pm_node_list_append(&assocs, assoc);
+        }
     }
 
     pm_hash_pattern_node_t *node = pm_hash_pattern_node_node_list_create(parser, &assocs, rest);


Test patch:
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 e218788c..bfdfa905 100644
--- a/test/prism/snapshots/seattlerb/case_in_hash_pat_rest.txt
+++ b/test/prism/snapshots/seattlerb/case_in_hash_pat_rest.txt
@@ -16,27 +16,27 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,15))
             │       │   ├── constant: ∅
-            │       │   ├── elements: (length: 2)
-            │       │   │   ├── @ AssocNode (location: (2,3)-(2,7))
-            │       │   │   │   ├── key:
-            │       │   │   │   │   @ SymbolNode (location: (2,3)-(2,5))
-            │       │   │   │   │   ├── flags: ∅
-            │       │   │   │   │   ├── opening_loc: ∅
-            │       │   │   │   │   ├── value_loc: (2,3)-(2,4) = "b"
-            │       │   │   │   │   ├── closing_loc: (2,4)-(2,5) = ":"
-            │       │   │   │   │   └── unescaped: "b"
-            │       │   │   │   ├── value:
-            │       │   │   │   │   @ LocalVariableTargetNode (location: (2,6)-(2,7))
-            │       │   │   │   │   ├── name: :c
-            │       │   │   │   │   └── depth: 0
-            │       │   │   │   └── operator_loc: ∅
-            │       │   │   └── @ AssocSplatNode (location: (2,9)-(2,15))
+            │       │   ├── elements: (length: 1)
+            │       │   │   └── @ AssocNode (location: (2,3)-(2,7))
+            │       │   │       ├── key:
+            │       │   │       │   @ SymbolNode (location: (2,3)-(2,5))
+            │       │   │       │   ├── flags: ∅
+            │       │   │       │   ├── opening_loc: ∅
+            │       │   │       │   ├── value_loc: (2,3)-(2,4) = "b"
+            │       │   │       │   ├── closing_loc: (2,4)-(2,5) = ":"
+            │       │   │       │   └── unescaped: "b"
             │       │   │       ├── value:
-            │       │   │       │   @ LocalVariableTargetNode (location: (2,11)-(2,15))
-            │       │   │       │   ├── name: :rest
+            │       │   │       │   @ LocalVariableTargetNode (location: (2,6)-(2,7))
+            │       │   │       │   ├── name: :c
             │       │   │       │   └── depth: 0
-            │       │   │       └── operator_loc: (2,9)-(2,11) = "**"
-            │       │   ├── rest: ∅
+            │       │   │       └── operator_loc: ∅
+            │       │   ├── rest:
+            │       │   │   @ AssocSplatNode (location: (2,9)-(2,15))
+            │       │   │   ├── value:
+            │       │   │   │   @ LocalVariableTargetNode (location: (2,11)-(2,15))
+            │       │   │   │   ├── name: :rest
+            │       │   │   │   └── depth: 0
+            │       │   │   └── operator_loc: (2,9)-(2,11) = "**"
             │       │   ├── 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 b4a1b265..d372ece2 100644
--- a/test/prism/snapshots/seattlerb/parse_pattern_058.txt
+++ b/test/prism/snapshots/seattlerb/parse_pattern_058.txt
@@ -26,24 +26,24 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,15))
             │       │   ├── constant: ∅
-            │       │   ├── elements: (length: 2)
-            │       │   │   ├── @ AssocNode (location: (2,4)-(2,6))
-            │       │   │   │   ├── key:
-            │       │   │   │   │   @ SymbolNode (location: (2,4)-(2,6))
-            │       │   │   │   │   ├── flags: ∅
-            │       │   │   │   │   ├── opening_loc: ∅
-            │       │   │   │   │   ├── value_loc: (2,4)-(2,5) = "a"
-            │       │   │   │   │   ├── closing_loc: (2,5)-(2,6) = ":"
-            │       │   │   │   │   └── unescaped: "a"
-            │       │   │   │   ├── value: ∅
-            │       │   │   │   └── operator_loc: ∅
-            │       │   │   └── @ AssocSplatNode (location: (2,8)-(2,14))
-            │       │   │       ├── value:
-            │       │   │       │   @ LocalVariableTargetNode (location: (2,10)-(2,14))
-            │       │   │       │   ├── name: :rest
-            │       │   │       │   └── depth: 0
-            │       │   │       └── operator_loc: (2,8)-(2,10) = "**"
-            │       │   ├── rest: ∅
+            │       │   ├── elements: (length: 1)
+            │       │   │   └── @ AssocNode (location: (2,4)-(2,6))
+            │       │   │       ├── key:
+            │       │   │       │   @ SymbolNode (location: (2,4)-(2,6))
+            │       │   │       │   ├── flags: ∅
+            │       │   │       │   ├── opening_loc: ∅
+            │       │   │       │   ├── value_loc: (2,4)-(2,5) = "a"
+            │       │   │       │   ├── closing_loc: (2,5)-(2,6) = ":"
+            │       │   │       │   └── unescaped: "a"
+            │       │   │       ├── value: ∅
+            │       │   │       └── operator_loc: ∅
+            │       │   ├── rest:
+            │       │   │   @ AssocSplatNode (location: (2,8)-(2,14))
+            │       │   │   ├── value:
+            │       │   │   │   @ LocalVariableTargetNode (location: (2,10)-(2,14))
+            │       │   │   │   ├── name: :rest
+            │       │   │   │   └── depth: 0
+            │       │   │   └── operator_loc: (2,8)-(2,10) = "**"
             │       │   ├── 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 f6625efc..b75b1989 100644
--- a/test/prism/snapshots/seattlerb/parse_pattern_058_2.txt
+++ b/test/prism/snapshots/seattlerb/parse_pattern_058_2.txt
@@ -26,21 +26,21 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,11))
             │       │   ├── constant: ∅
-            │       │   ├── elements: (length: 2)
-            │       │   │   ├── @ AssocNode (location: (2,4)-(2,6))
-            │       │   │   │   ├── key:
-            │       │   │   │   │   @ SymbolNode (location: (2,4)-(2,6))
-            │       │   │   │   │   ├── flags: ∅
-            │       │   │   │   │   ├── opening_loc: ∅
-            │       │   │   │   │   ├── value_loc: (2,4)-(2,5) = "a"
-            │       │   │   │   │   ├── closing_loc: (2,5)-(2,6) = ":"
-            │       │   │   │   │   └── unescaped: "a"
-            │       │   │   │   ├── value: ∅
-            │       │   │   │   └── operator_loc: ∅
-            │       │   │   └── @ AssocSplatNode (location: (2,8)-(2,10))
+            │       │   ├── elements: (length: 1)
+            │       │   │   └── @ AssocNode (location: (2,4)-(2,6))
+            │       │   │       ├── key:
+            │       │   │       │   @ SymbolNode (location: (2,4)-(2,6))
+            │       │   │       │   ├── flags: ∅
+            │       │   │       │   ├── opening_loc: ∅
+            │       │   │       │   ├── value_loc: (2,4)-(2,5) = "a"
+            │       │   │       │   ├── closing_loc: (2,5)-(2,6) = ":"
+            │       │   │       │   └── unescaped: "a"
             │       │   │       ├── value: ∅
-            │       │   │       └── operator_loc: (2,8)-(2,10) = "**"
-            │       │   ├── rest: ∅
+            │       │   │       └── operator_loc: ∅
+            │       │   ├── rest:
+            │       │   │   @ AssocSplatNode (location: (2,8)-(2,10))
+            │       │   │   ├── value: ∅
+            │       │   │   └── operator_loc: (2,8)-(2,10) = "**"
             │       │   ├── opening_loc: (2,3)-(2,4) = "{"
             │       │   └── closing_loc: (2,10)-(2,11) = "}"
             │       ├── statements:
diff --git a/test/prism/snapshots/seattlerb/parse_pattern_076.txt b/test/prism/snapshots/seattlerb/parse_pattern_076.txt
index 7cf05dda..8f4ef571 100644
--- a/test/prism/snapshots/seattlerb/parse_pattern_076.txt
+++ b/test/prism/snapshots/seattlerb/parse_pattern_076.txt
@@ -26,23 +26,23 @@
             │       ├── pattern:
             │       │   @ HashPatternNode (location: (2,3)-(2,16))
             │       │   ├── constant: ∅
-            │       │   ├── elements: (length: 2)
-            │       │   │   ├── @ AssocNode (location: (2,4)-(2,8))
-            │       │   │   │   ├── key:
-            │       │   │   │   │   @ SymbolNode (location: (2,4)-(2,6))
-            │       │   │   │   │   ├── flags: ∅
-            │       │   │   │   │   ├── opening_loc: ∅
-            │       │   │   │   │   ├── value_loc: (2,4)-(2,5) = "a"
-            │       │   │   │   │   ├── closing_loc: (2,5)-(2,6) = ":"
-            │       │   │   │   │   └── unescaped: "a"
-            │       │   │   │   ├── value:
-            │       │   │   │   │   @ IntegerNode (location: (2,7)-(2,8))
-            │       │   │   │   │   └── flags: decimal
-            │       │   │   │   └── operator_loc: ∅
-            │       │   │   └── @ NoKeywordsParameterNode (location: (2,10)-(2,15))
-            │       │   │       ├── operator_loc: (2,10)-(2,12) = "**"
-            │       │   │       └── keyword_loc: (2,12)-(2,15) = "nil"
-            │       │   ├── rest: ∅
+            │       │   ├── elements: (length: 1)
+            │       │   │   └── @ AssocNode (location: (2,4)-(2,8))
+            │       │   │       ├── key:
+            │       │   │       │   @ SymbolNode (location: (2,4)-(2,6))
+            │       │   │       │   ├── flags: ∅
+            │       │   │       │   ├── opening_loc: ∅
+            │       │   │       │   ├── value_loc: (2,4)-(2,5) = "a"
+            │       │   │       │   ├── closing_loc: (2,5)-(2,6) = ":"
+            │       │   │       │   └── unescaped: "a"
+            │       │   │       ├── value:
+            │       │   │       │   @ IntegerNode (location: (2,7)-(2,8))
+            │       │   │       │   └── flags: decimal
+            │       │   │       └── operator_loc: ∅
+            │       │   ├── rest:
+            │       │   │   @ NoKeywordsParameterNode (location: (2,10)-(2,15))
+            │       │   │   ├── operator_loc: (2,10)-(2,12) = "**"
+            │       │   │   └── keyword_loc: (2,12)-(2,15) = "nil"
             │       │   ├── opening_loc: (2,3)-(2,4) = "{"
             │       │   └── closing_loc: (2,15)-(2,16) = "}"
             │       ├── statements: