Repository: ruby/yarp (c)

Issue:
Request for way to know omitted hash value type
For hash value omission like `{ lvar: }`, `{ method: }`, `{ A: }`, I want to know the type of omitted value.

```ruby
YARP.parse("a=1;{a:}").value.statements.body[1]
# => HashNode(4...8)((4...5), [AssocNode(5...7)(SymbolNode(5...7)(nil, (5...6), (6...7), "a"), nil, nil)], (7...8))
YARP.parse("b=1;{a:}").value.statements.body[1]
# => HashNode(4...8)((4...5), [AssocNode(5...7)(SymbolNode(5...7)(nil, (5...6), (6...7), "a"), nil, nil)], (7...8))
```

For RubyVM::AST, it can be distinguished by `LVAR` `VCALL`.
```ruby
RubyVM::AbstractSyntaxTree.parse "a=1;{a:}"
# => 
(SCOPE@1:0-1:8
 tbl: [:a]
 args: nil
 body: (BLOCK@1:0-1:8 (LASGN@1:0-1:3 :a (LIT@1:2-1:3 1)) (HASH@1:4-1:8 (LIST@1:5-1:7 (LIT@1:5-1:7 :a) (LVAR@1:5-1:7 :a) nil))))

RubyVM::AbstractSyntaxTree.parse "b=1;{a:}"
# => 
(SCOPE@1:0-1:8
 tbl: [:b]
 args: nil
 body: (BLOCK@1:0-1:8 (LASGN@1:0-1:3 :b (LIT@1:2-1:3 1)) (HASH@1:4-1:8 (LIST@1:5-1:7 (LIT@1:5-1:7 :a) (VCALL@1:5-1:7 :a) nil))))

# Difference: (LVAR@1:5-1:7 :a) vs (VCALL@1:5-1:7 :a)
```


Pull request description:
Add implicit nodes for ommitted hash values
Fixes #1447

We now always insert an implicit node into a hash value when the hash value is omitted. This should make it easier on compilers and static analysis tools to determine what is actually going on without having to look at the local tables themselves. Because we have different nodes types for these nodes, tools can still consistently handle/ignore them as they like. As a bonus, all of the implicit nodes are very small.

Patch:
diff --git a/config.yml b/config.yml
index b446742e..fbaea52f 100644
--- a/config.yml
+++ b/config.yml
@@ -1413,6 +1413,19 @@ nodes:
 
           1.0i
           ^^^^
+  - name: ImplicitNode
+    fields:
+      - name: value
+        type: node
+    comment: |
+      Represents a node that is implicitly being added to the tree but doesn't
+      correspond directly to a node in the source.
+
+          { foo: }
+            ^^^^
+
+          { Foo: }
+            ^^^^
   - name: InNode
     fields:
       - name: pattern
diff --git a/src/yarp.c b/src/yarp.c
index d271627e..3eabc556 100644
--- a/src/yarp.c
+++ b/src/yarp.c
@@ -2737,6 +2737,22 @@ yp_else_node_end_keyword_loc_set(yp_else_node_t *node, const yp_token_t *keyword
     node->end_keyword_loc = YP_LOCATION_TOKEN_VALUE(keyword);
 }
 
+// Allocate and initialize a new ImplicitNode node.
+static yp_implicit_node_t *
+yp_implicit_node_create(yp_parser_t *parser, yp_node_t *value) {
+    yp_implicit_node_t *node = YP_ALLOC_NODE(parser, yp_implicit_node_t);
+
+    *node = (yp_implicit_node_t) {
+        {
+            .type = YP_IMPLICIT_NODE,
+            .location = value->location
+        },
+        .value = value
+    };
+
+    return node;
+}
+
 // Allocate and initialize a new IntegerNode node.
 static yp_integer_node_t *
 yp_integer_node_create(yp_parser_t *parser, yp_node_flags_t base, const yp_token_t *token) {
@@ -8715,14 +8731,32 @@ parse_assocs(yp_parser_t *parser, yp_node_t *node) {
                 break;
             }
             case YP_TOKEN_LABEL: {
+                yp_token_t label = parser->current;
                 parser_lex(parser);
 
-                yp_node_t *key = (yp_node_t *) yp_symbol_node_label_create(parser, &parser->previous);
+                yp_node_t *key = (yp_node_t *) yp_symbol_node_label_create(parser, &label);
                 yp_token_t operator = not_provided(parser);
                 yp_node_t *value = NULL;
 
                 if (token_begins_expression_p(parser->current.type)) {
                     value = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_HASH_EXPRESSION_AFTER_LABEL);
+                } else {
+                    if (parser->encoding.isupper_char(label.start, (label.end - 1) - label.start)) {
+                        yp_token_t constant = { .type = YP_TOKEN_CONSTANT, .start = label.start, .end = label.end - 1 };
+                        value = (yp_node_t *) yp_constant_read_node_create(parser, &constant);
+                    } else {
+                        int depth = yp_parser_local_depth(parser, &((yp_token_t) { .type = YP_TOKEN_IDENTIFIER, .start = label.start, .end = label.end - 1 }));
+                        yp_token_t identifier = { .type = YP_TOKEN_IDENTIFIER, .start = label.start, .end = label.end - 1 };
+
+                        if (depth == -1) {
+                            value = (yp_node_t *) yp_call_node_variable_call_create(parser, &identifier);
+                        } else {
+                            value = (yp_node_t *) yp_local_variable_read_node_create(parser, &identifier, (uint32_t) depth);
+                        }
+                    }
+
+                    value->location.end++;
+                    value = (yp_node_t *) yp_implicit_node_create(parser, value);
                 }
 
                 element = (yp_node_t *) yp_assoc_node_create(parser, key, &operator, value);


Test patch:
diff --git a/test/yarp/fixtures/hashes.txt b/test/yarp/fixtures/hashes.txt
index 42c430e4..443b2359 100644
--- a/test/yarp/fixtures/hashes.txt
+++ b/test/yarp/fixtures/hashes.txt
@@ -18,3 +18,9 @@
 { a: b, c: d, **e, f: g }
 
 { "a": !b? }
+
+a = 1
+tap do
+  b = 1
+  { a:, b:, c:, D: }
+end
diff --git a/test/yarp/location_test.rb b/test/yarp/location_test.rb
index 3ef3da3f..1c9aa104 100644
--- a/test/yarp/location_test.rb
+++ b/test/yarp/location_test.rb
@@ -410,6 +410,20 @@ module YARP
       assert_location(ImaginaryNode, "1ri")
     end
 
+    def test_ImplicitNode
+      assert_location(ImplicitNode, "{ foo: }", 2...6) do |node|
+        node.elements.first.value
+      end
+
+      assert_location(ImplicitNode, "{ Foo: }", 2..6) do |node|
+        node.elements.first.value
+      end
+
+      assert_location(ImplicitNode, "foo = 1; { foo: }", 11..15) do |node|
+        node.elements.first.value
+      end
+    end
+
     def test_InNode
       assert_location(InNode, "case foo; in bar; end", 10...16) do |node|
         node.conditions.first
diff --git a/test/yarp/snapshots/hashes.txt b/test/yarp/snapshots/hashes.txt
index dde8ba01..b49db786 100644
--- a/test/yarp/snapshots/hashes.txt
+++ b/test/yarp/snapshots/hashes.txt
@@ -1,8 +1,8 @@
-@ ProgramNode (location: (0...120))
-├── locals: []
+@ ProgramNode (location: (0...167))
+├── locals: [:a]
 └── statements:
-    @ StatementsNode (location: (0...120))
-    └── body: (length: 7)
+    @ StatementsNode (location: (0...167))
+    └── body: (length: 9)
         ├── @ HashNode (location: (0...2))
         │   ├── opening_loc: (0...1) = "{"
         │   ├── elements: (length: 0)
@@ -220,36 +220,136 @@
         │   │       │   └── name: "g"
         │   │       └── operator_loc: ∅
         │   └── closing_loc: (105...106) = "}"
-        └── @ HashNode (location: (108...120))
-            ├── opening_loc: (108...109) = "{"
-            ├── elements: (length: 1)
-            │   └── @ AssocNode (location: (110...118))
-            │       ├── key:
-            │       │   @ SymbolNode (location: (110...114))
-            │       │   ├── opening_loc: (110...111) = "\""
-            │       │   ├── value_loc: (111...112) = "a"
-            │       │   ├── closing_loc: (112...114) = "\":"
-            │       │   └── unescaped: "a"
-            │       ├── value:
-            │       │   @ CallNode (location: (115...118))
-            │       │   ├── receiver:
-            │       │   │   @ CallNode (location: (116...118))
-            │       │   │   ├── receiver: ∅
-            │       │   │   ├── call_operator_loc: ∅
-            │       │   │   ├── message_loc: (116...118) = "b?"
-            │       │   │   ├── opening_loc: ∅
-            │       │   │   ├── arguments: ∅
-            │       │   │   ├── closing_loc: ∅
-            │       │   │   ├── block: ∅
-            │       │   │   ├── flags: ∅
-            │       │   │   └── name: "b?"
-            │       │   ├── call_operator_loc: ∅
-            │       │   ├── message_loc: (115...116) = "!"
-            │       │   ├── opening_loc: ∅
-            │       │   ├── arguments: ∅
-            │       │   ├── closing_loc: ∅
-            │       │   ├── block: ∅
-            │       │   ├── flags: ∅
-            │       │   └── name: "!"
-            │       └── operator_loc: ∅
-            └── closing_loc: (119...120) = "}"
+        ├── @ HashNode (location: (108...120))
+        │   ├── opening_loc: (108...109) = "{"
+        │   ├── elements: (length: 1)
+        │   │   └── @ AssocNode (location: (110...118))
+        │   │       ├── key:
+        │   │       │   @ SymbolNode (location: (110...114))
+        │   │       │   ├── opening_loc: (110...111) = "\""
+        │   │       │   ├── value_loc: (111...112) = "a"
+        │   │       │   ├── closing_loc: (112...114) = "\":"
+        │   │       │   └── unescaped: "a"
+        │   │       ├── value:
+        │   │       │   @ CallNode (location: (115...118))
+        │   │       │   ├── receiver:
+        │   │       │   │   @ CallNode (location: (116...118))
+        │   │       │   │   ├── receiver: ∅
+        │   │       │   │   ├── call_operator_loc: ∅
+        │   │       │   │   ├── message_loc: (116...118) = "b?"
+        │   │       │   │   ├── opening_loc: ∅
+        │   │       │   │   ├── arguments: ∅
+        │   │       │   │   ├── closing_loc: ∅
+        │   │       │   │   ├── block: ∅
+        │   │       │   │   ├── flags: ∅
+        │   │       │   │   └── name: "b?"
+        │   │       │   ├── call_operator_loc: ∅
+        │   │       │   ├── message_loc: (115...116) = "!"
+        │   │       │   ├── opening_loc: ∅
+        │   │       │   ├── arguments: ∅
+        │   │       │   ├── closing_loc: ∅
+        │   │       │   ├── block: ∅
+        │   │       │   ├── flags: ∅
+        │   │       │   └── name: "!"
+        │   │       └── operator_loc: ∅
+        │   └── closing_loc: (119...120) = "}"
+        ├── @ LocalVariableWriteNode (location: (122...127))
+        │   ├── name: :a
+        │   ├── depth: 0
+        │   ├── name_loc: (122...123) = "a"
+        │   ├── value:
+        │   │   @ IntegerNode (location: (126...127))
+        │   │   └── flags: decimal
+        │   └── operator_loc: (124...125) = "="
+        └── @ CallNode (location: (128...167))
+            ├── receiver: ∅
+            ├── call_operator_loc: ∅
+            ├── message_loc: (128...131) = "tap"
+            ├── opening_loc: ∅
+            ├── arguments: ∅
+            ├── closing_loc: ∅
+            ├── block:
+            │   @ BlockNode (location: (132...167))
+            │   ├── locals: [:b]
+            │   ├── parameters: ∅
+            │   ├── body:
+            │   │   @ StatementsNode (location: (137...163))
+            │   │   └── body: (length: 2)
+            │   │       ├── @ LocalVariableWriteNode (location: (137...142))
+            │   │       │   ├── name: :b
+            │   │       │   ├── depth: 0
+            │   │       │   ├── name_loc: (137...138) = "b"
+            │   │       │   ├── value:
+            │   │       │   │   @ IntegerNode (location: (141...142))
+            │   │       │   │   └── flags: decimal
+            │   │       │   └── operator_loc: (139...140) = "="
+            │   │       └── @ HashNode (location: (145...163))
+            │   │           ├── opening_loc: (145...146) = "{"
+            │   │           ├── elements: (length: 4)
+            │   │           │   ├── @ AssocNode (location: (147...149))
+            │   │           │   │   ├── key:
+            │   │           │   │   │   @ SymbolNode (location: (147...149))
+            │   │           │   │   │   ├── opening_loc: ∅
+            │   │           │   │   │   ├── value_loc: (147...148) = "a"
+            │   │           │   │   │   ├── closing_loc: (148...149) = ":"
+            │   │           │   │   │   └── unescaped: "a"
+            │   │           │   │   ├── value:
+            │   │           │   │   │   @ ImplicitNode (location: (147...149))
+            │   │           │   │   │   └── value:
+            │   │           │   │   │       @ LocalVariableReadNode (location: (147...149))
+            │   │           │   │   │       ├── name: :a
+            │   │           │   │   │       └── depth: 1
+            │   │           │   │   └── operator_loc: ∅
+            │   │           │   ├── @ AssocNode (location: (151...153))
+            │   │           │   │   ├── key:
+            │   │           │   │   │   @ SymbolNode (location: (151...153))
+            │   │           │   │   │   ├── opening_loc: ∅
+            │   │           │   │   │   ├── value_loc: (151...152) = "b"
+            │   │           │   │   │   ├── closing_loc: (152...153) = ":"
+            │   │           │   │   │   └── unescaped: "b"
+            │   │           │   │   ├── value:
+            │   │           │   │   │   @ ImplicitNode (location: (151...153))
+            │   │           │   │   │   └── value:
+            │   │           │   │   │       @ LocalVariableReadNode (location: (151...153))
+            │   │           │   │   │       ├── name: :b
+            │   │           │   │   │       └── depth: 0
+            │   │           │   │   └── operator_loc: ∅
+            │   │           │   ├── @ AssocNode (location: (155...157))
+            │   │           │   │   ├── key:
+            │   │           │   │   │   @ SymbolNode (location: (155...157))
+            │   │           │   │   │   ├── opening_loc: ∅
+            │   │           │   │   │   ├── value_loc: (155...156) = "c"
+            │   │           │   │   │   ├── closing_loc: (156...157) = ":"
+            │   │           │   │   │   └── unescaped: "c"
+            │   │           │   │   ├── value:
+            │   │           │   │   │   @ ImplicitNode (location: (155...157))
+            │   │           │   │   │   └── value:
+            │   │           │   │   │       @ CallNode (location: (155...157))
+            │   │           │   │   │       ├── receiver: ∅
+            │   │           │   │   │       ├── call_operator_loc: ∅
+            │   │           │   │   │       ├── message_loc: (155...156) = "c"
+            │   │           │   │   │       ├── opening_loc: ∅
+            │   │           │   │   │       ├── arguments: ∅
+            │   │           │   │   │       ├── closing_loc: ∅
+            │   │           │   │   │       ├── block: ∅
+            │   │           │   │   │       ├── flags: ∅
+            │   │           │   │   │       └── name: "c"
+            │   │           │   │   └── operator_loc: ∅
+            │   │           │   └── @ AssocNode (location: (159...161))
+            │   │           │       ├── key:
+            │   │           │       │   @ SymbolNode (location: (159...161))
+            │   │           │       │   ├── opening_loc: ∅
+            │   │           │       │   ├── value_loc: (159...160) = "D"
+            │   │           │       │   ├── closing_loc: (160...161) = ":"
+            │   │           │       │   └── unescaped: "D"
+            │   │           │       ├── value:
+            │   │           │       │   @ ImplicitNode (location: (159...161))
+            │   │           │       │   └── value:
+            │   │           │       │       @ ConstantReadNode (location: (159...161))
+            │   │           │       │       └── name: :D
+            │   │           │       └── operator_loc: ∅
+            │   │           └── closing_loc: (162...163) = "}"
+            │   ├── opening_loc: (132...134) = "do"
+            │   └── closing_loc: (164...167) = "end"
+            ├── flags: ∅
+            └── name: "tap"
diff --git a/test/yarp/snapshots/if.txt b/test/yarp/snapshots/if.txt
index ebafafe1..7feb336d 100644
--- a/test/yarp/snapshots/if.txt
+++ b/test/yarp/snapshots/if.txt
@@ -240,7 +240,19 @@
         │   │           │                   │   ├── value_loc: (248...249) = "b"
         │   │           │                   │   ├── closing_loc: (249...250) = ":"
         │   │           │                   │   └── unescaped: "b"
-        │   │           │                   ├── value: ∅
+        │   │           │                   ├── value:
+        │   │           │                   │   @ ImplicitNode (location: (248...250))
+        │   │           │                   │   └── value:
+        │   │           │                   │       @ CallNode (location: (248...250))
+        │   │           │                   │       ├── receiver: ∅
+        │   │           │                   │       ├── call_operator_loc: ∅
+        │   │           │                   │       ├── message_loc: (248...249) = "b"
+        │   │           │                   │       ├── opening_loc: ∅
+        │   │           │                   │       ├── arguments: ∅
+        │   │           │                   │       ├── closing_loc: ∅
+        │   │           │                   │       ├── block: ∅
+        │   │           │                   │       ├── flags: ∅
+        │   │           │                   │       └── name: "b"
         │   │           │                   └── operator_loc: ∅
         │   │           ├── closing_loc: ∅
         │   │           ├── block: ∅
diff --git a/test/yarp/snapshots/rescue.txt b/test/yarp/snapshots/rescue.txt
index 3176c864..40b84f7b 100644
--- a/test/yarp/snapshots/rescue.txt
+++ b/test/yarp/snapshots/rescue.txt
@@ -340,7 +340,19 @@
             │   │           │                   │   ├── value_loc: (303...304) = "b"
             │   │           │                   │   ├── closing_loc: (304...305) = ":"
             │   │           │                   │   └── unescaped: "b"
-            │   │           │                   ├── value: ∅
+            │   │           │                   ├── value:
+            │   │           │                   │   @ ImplicitNode (location: (303...305))
+            │   │           │                   │   └── value:
+            │   │           │                   │       @ CallNode (location: (303...305))
+            │   │           │                   │       ├── receiver: ∅
+            │   │           │                   │       ├── call_operator_loc: ∅
+            │   │           │                   │       ├── message_loc: (303...304) = "b"
+            │   │           │                   │       ├── opening_loc: ∅
+            │   │           │                   │       ├── arguments: ∅
+            │   │           │                   │       ├── closing_loc: ∅
+            │   │           │                   │       ├── block: ∅
+            │   │           │                   │       ├── flags: ∅
+            │   │           │                   │       └── name: "b"
             │   │           │                   └── operator_loc: ∅
             │   │           ├── closing_loc: ∅
             │   │           ├── block: ∅
diff --git a/test/yarp/snapshots/seattlerb/assoc__bare.txt b/test/yarp/snapshots/seattlerb/assoc__bare.txt
index 87ae0f72..fa86e4fa 100644
--- a/test/yarp/snapshots/seattlerb/assoc__bare.txt
+++ b/test/yarp/snapshots/seattlerb/assoc__bare.txt
@@ -13,6 +13,18 @@
             │       │   ├── value_loc: (2...3) = "y"
             │       │   ├── closing_loc: (3...4) = ":"
             │       │   └── unescaped: "y"
-            │       ├── value: ∅
+            │       ├── value:
+            │       │   @ ImplicitNode (location: (2...4))
+            │       │   └── value:
+            │       │       @ CallNode (location: (2...4))
+            │       │       ├── receiver: ∅
+            │       │       ├── call_operator_loc: ∅
+            │       │       ├── message_loc: (2...3) = "y"
+            │       │       ├── opening_loc: ∅
+            │       │       ├── arguments: ∅
+            │       │       ├── closing_loc: ∅
+            │       │       ├── block: ∅
+            │       │       ├── flags: ∅
+            │       │       └── name: "y"
             │       └── operator_loc: ∅
             └── closing_loc: (5...6) = "}"
diff --git a/test/yarp/snapshots/whitequark/hash_pair_value_omission.txt b/test/yarp/snapshots/whitequark/hash_pair_value_omission.txt
index 09d4853a..9b6d43af 100644
--- a/test/yarp/snapshots/whitequark/hash_pair_value_omission.txt
+++ b/test/yarp/snapshots/whitequark/hash_pair_value_omission.txt
@@ -13,7 +13,11 @@
         │   │       │   ├── value_loc: (1...4) = "BAR"
         │   │       │   ├── closing_loc: (4...5) = ":"
         │   │       │   └── unescaped: "BAR"
-        │   │       ├── value: ∅
+        │   │       ├── value:
+        │   │       │   @ ImplicitNode (location: (1...5))
+        │   │       │   └── value:
+        │   │       │       @ ConstantReadNode (location: (1...5))
+        │   │       │       └── name: :BAR
         │   │       └── operator_loc: ∅
         │   └── closing_loc: (5...6) = "}"
         ├── @ HashNode (location: (8...16))
@@ -26,7 +30,19 @@
         │   │   │   │   ├── value_loc: (9...10) = "a"
         │   │   │   │   ├── closing_loc: (10...11) = ":"
         │   │   │   │   └── unescaped: "a"
-        │   │   │   ├── value: ∅
+        │   │   │   ├── value:
+        │   │   │   │   @ ImplicitNode (location: (9...11))
+        │   │   │   │   └── value:
+        │   │   │   │       @ CallNode (location: (9...11))
+        │   │   │   │       ├── receiver: ∅
+        │   │   │   │       ├── call_operator_loc: ∅
+        │   │   │   │       ├── message_loc: (9...10) = "a"
+        │   │   │   │       ├── opening_loc: ∅
+        │   │   │   │       ├── arguments: ∅
+        │   │   │   │       ├── closing_loc: ∅
+        │   │   │   │       ├── block: ∅
+        │   │   │   │       ├── flags: ∅
+        │   │   │   │       └── name: "a"
         │   │   │   └── operator_loc: ∅
         │   │   └── @ AssocNode (location: (13...15))
         │   │       ├── key:
@@ -35,7 +51,19 @@
         │   │       │   ├── value_loc: (13...14) = "b"
         │   │       │   ├── closing_loc: (14...15) = ":"
         │   │       │   └── unescaped: "b"
-        │   │       ├── value: ∅
+        │   │       ├── value:
+        │   │       │   @ ImplicitNode (location: (13...15))
+        │   │       │   └── value:
+        │   │       │       @ CallNode (location: (13...15))
+        │   │       │       ├── receiver: ∅
+        │   │       │       ├── call_operator_loc: ∅
+        │   │       │       ├── message_loc: (13...14) = "b"
+        │   │       │       ├── opening_loc: ∅
+        │   │       │       ├── arguments: ∅
+        │   │       │       ├── closing_loc: ∅
+        │   │       │       ├── block: ∅
+        │   │       │       ├── flags: ∅
+        │   │       │       └── name: "b"
         │   │       └── operator_loc: ∅
         │   └── closing_loc: (15...16) = "}"
         └── @ HashNode (location: (18...25))
@@ -48,6 +76,18 @@
             │       │   ├── value_loc: (19...23) = "puts"
             │       │   ├── closing_loc: (23...24) = ":"
             │       │   └── unescaped: "puts"
-            │       ├── value: ∅
+            │       ├── value:
+            │       │   @ ImplicitNode (location: (19...24))
+            │       │   └── value:
+            │       │       @ CallNode (location: (19...24))
+            │       │       ├── receiver: ∅
+            │       │       ├── call_operator_loc: ∅
+            │       │       ├── message_loc: (19...23) = "puts"
+            │       │       ├── opening_loc: ∅
+            │       │       ├── arguments: ∅
+            │       │       ├── closing_loc: ∅
+            │       │       ├── block: ∅
+            │       │       ├── flags: ∅
+            │       │       └── name: "puts"
             │       └── operator_loc: ∅
             └── closing_loc: (24...25) = "}"
diff --git a/test/yarp/snapshots/whitequark/keyword_argument_omission.txt b/test/yarp/snapshots/whitequark/keyword_argument_omission.txt
index 6808bf12..bbcb0d36 100644
--- a/test/yarp/snapshots/whitequark/keyword_argument_omission.txt
+++ b/test/yarp/snapshots/whitequark/keyword_argument_omission.txt
@@ -20,7 +20,19 @@
             │               │   │   ├── value_loc: (4...5) = "a"
             │               │   │   ├── closing_loc: (5...6) = ":"
             │               │   │   └── unescaped: "a"
-            │               │   ├── value: ∅
+            │               │   ├── value:
+            │               │   │   @ ImplicitNode (location: (4...6))
+            │               │   │   └── value:
+            │               │   │       @ CallNode (location: (4...6))
+            │               │   │       ├── receiver: ∅
+            │               │   │       ├── call_operator_loc: ∅
+            │               │   │       ├── message_loc: (4...5) = "a"
+            │               │   │       ├── opening_loc: ∅
+            │               │   │       ├── arguments: ∅
+            │               │   │       ├── closing_loc: ∅
+            │               │   │       ├── block: ∅
+            │               │   │       ├── flags: ∅
+            │               │   │       └── name: "a"
             │               │   └── operator_loc: ∅
             │               └── @ AssocNode (location: (8...10))
             │                   ├── key:
@@ -29,7 +41,19 @@
             │                   │   ├── value_loc: (8...9) = "b"
             │                   │   ├── closing_loc: (9...10) = ":"
             │                   │   └── unescaped: "b"
-            │                   ├── value: ∅
+            │                   ├── value:
+            │                   │   @ ImplicitNode (location: (8...10))
+            │                   │   └── value:
+            │                   │       @ CallNode (location: (8...10))
+            │                   │       ├── receiver: ∅
+            │                   │       ├── call_operator_loc: ∅
+            │                   │       ├── message_loc: (8...9) = "b"
+            │                   │       ├── opening_loc: ∅
+            │                   │       ├── arguments: ∅
+            │                   │       ├── closing_loc: ∅
+            │                   │       ├── block: ∅
+            │                   │       ├── flags: ∅
+            │                   │       └── name: "b"
             │                   └── operator_loc: ∅
             ├── closing_loc: (10...11) = ")"
             ├── block: ∅