Skip to content

Commit 722af59

Browse files
committed
Allow command calls in endless method bodies within assignments
Previously, endless method definitions in assignment contexts like `x = def f = p 1` would fail to parse because command calls (method calls without parentheses) were only accepted when the surrounding binding power was less than `PM_BINDING_POWER_COMPOSITION`. This fix specifically checks for assignment context and allows command calls in those cases while maintaining the existing behavior for other contexts. This ensures that: - `x = def f = p 1` parses correctly (previously failed) - `private def f = puts "Hello"` still produces the expected error
1 parent dee6c23 commit 722af59

File tree

4 files changed

+108
-55
lines changed

4 files changed

+108
-55
lines changed

snapshots/endless_methods.txt

Lines changed: 94 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
@ ProgramNode (location: (1,0)-(5,22))
1+
@ ProgramNode (location: (1,0)-(7,15))
22
├── flags: ∅
3-
├── locals: []
3+
├── locals: [:x]
44
└── statements:
5-
@ StatementsNode (location: (1,0)-(5,22))
5+
@ StatementsNode (location: (1,0)-(7,15))
66
├── flags: ∅
7-
└── body: (length: 3)
7+
└── body: (length: 4)
88
├── @ DefNode (location: (1,0)-(1,11))
99
│ ├── flags: newline
1010
│ ├── name: :foo
@@ -61,55 +61,95 @@
6161
│ ├── rparen_loc: ∅
6262
│ ├── equal_loc: (3,8)-(3,9) = "="
6363
│ └── end_keyword_loc: ∅
64-
└── @ DefNode (location: (5,0)-(5,22))
64+
├── @ DefNode (location: (5,0)-(5,22))
65+
│ ├── flags: newline
66+
│ ├── name: :method
67+
│ ├── name_loc: (5,4)-(5,10) = "method"
68+
│ ├── receiver: ∅
69+
│ ├── parameters: ∅
70+
│ ├── body:
71+
│ │ @ StatementsNode (location: (5,13)-(5,22))
72+
│ │ ├── flags: ∅
73+
│ │ └── body: (length: 1)
74+
│ │ └── @ CallNode (location: (5,13)-(5,22))
75+
│ │ ├── flags: ∅
76+
│ │ ├── receiver:
77+
│ │ │ @ CallNode (location: (5,13)-(5,18))
78+
│ │ │ ├── flags: ∅
79+
│ │ │ ├── receiver:
80+
│ │ │ │ @ IntegerNode (location: (5,13)-(5,14))
81+
│ │ │ │ ├── flags: static_literal, decimal
82+
│ │ │ │ └── value: 1
83+
│ │ │ ├── call_operator_loc: ∅
84+
│ │ │ ├── name: :+
85+
│ │ │ ├── message_loc: (5,15)-(5,16) = "+"
86+
│ │ │ ├── opening_loc: ∅
87+
│ │ │ ├── arguments:
88+
│ │ │ │ @ ArgumentsNode (location: (5,17)-(5,18))
89+
│ │ │ │ ├── flags: ∅
90+
│ │ │ │ └── arguments: (length: 1)
91+
│ │ │ │ └── @ IntegerNode (location: (5,17)-(5,18))
92+
│ │ │ │ ├── flags: static_literal, decimal
93+
│ │ │ │ └── value: 2
94+
│ │ │ ├── closing_loc: ∅
95+
│ │ │ └── block: ∅
96+
│ │ ├── call_operator_loc: ∅
97+
│ │ ├── name: :+
98+
│ │ ├── message_loc: (5,19)-(5,20) = "+"
99+
│ │ ├── opening_loc: ∅
100+
│ │ ├── arguments:
101+
│ │ │ @ ArgumentsNode (location: (5,21)-(5,22))
102+
│ │ │ ├── flags: ∅
103+
│ │ │ └── arguments: (length: 1)
104+
│ │ │ └── @ IntegerNode (location: (5,21)-(5,22))
105+
│ │ │ ├── flags: static_literal, decimal
106+
│ │ │ └── value: 3
107+
│ │ ├── closing_loc: ∅
108+
│ │ └── block: ∅
109+
│ ├── locals: []
110+
│ ├── def_keyword_loc: (5,0)-(5,3) = "def"
111+
│ ├── operator_loc: ∅
112+
│ ├── lparen_loc: ∅
113+
│ ├── rparen_loc: ∅
114+
│ ├── equal_loc: (5,11)-(5,12) = "="
115+
│ └── end_keyword_loc: ∅
116+
└── @ LocalVariableWriteNode (location: (7,0)-(7,15))
65117
├── flags: newline
66-
├── name: :method
67-
├── name_loc: (5,4)-(5,10) = "method"
68-
├── receiver: ∅
69-
├── parameters: ∅
70-
├── body:
71-
│ @ StatementsNode (location: (5,13)-(5,22))
118+
├── name: :x
119+
├── depth: 0
120+
├── name_loc: (7,0)-(7,1) = "x"
121+
├── value:
122+
│ @ DefNode (location: (7,4)-(7,15))
72123
│ ├── flags: ∅
73-
│ └── body: (length: 1)
74-
│ └── @ CallNode (location: (5,13)-(5,22))
75-
│ ├── flags: ∅
76-
│ ├── receiver:
77-
│ │ @ CallNode (location: (5,13)-(5,18))
78-
│ │ ├── flags: ∅
79-
│ │ ├── receiver:
80-
│ │ │ @ IntegerNode (location: (5,13)-(5,14))
81-
│ │ │ ├── flags: static_literal, decimal
82-
│ │ │ └── value: 1
83-
│ │ ├── call_operator_loc: ∅
84-
│ │ ├── name: :+
85-
│ │ ├── message_loc: (5,15)-(5,16) = "+"
86-
│ │ ├── opening_loc: ∅
87-
│ │ ├── arguments:
88-
│ │ │ @ ArgumentsNode (location: (5,17)-(5,18))
89-
│ │ │ ├── flags: ∅
90-
│ │ │ └── arguments: (length: 1)
91-
│ │ │ └── @ IntegerNode (location: (5,17)-(5,18))
92-
│ │ │ ├── flags: static_literal, decimal
93-
│ │ │ └── value: 2
94-
│ │ ├── closing_loc: ∅
95-
│ │ └── block: ∅
96-
│ ├── call_operator_loc: ∅
97-
│ ├── name: :+
98-
│ ├── message_loc: (5,19)-(5,20) = "+"
99-
│ ├── opening_loc: ∅
100-
│ ├── arguments:
101-
│ │ @ ArgumentsNode (location: (5,21)-(5,22))
102-
│ │ ├── flags: ∅
103-
│ │ └── arguments: (length: 1)
104-
│ │ └── @ IntegerNode (location: (5,21)-(5,22))
105-
│ │ ├── flags: static_literal, decimal
106-
│ │ └── value: 3
107-
│ ├── closing_loc: ∅
108-
│ └── block: ∅
109-
├── locals: []
110-
├── def_keyword_loc: (5,0)-(5,3) = "def"
111-
├── operator_loc: ∅
112-
├── lparen_loc: ∅
113-
├── rparen_loc: ∅
114-
├── equal_loc: (5,11)-(5,12) = "="
115-
└── end_keyword_loc: ∅
124+
│ ├── name: :f
125+
│ ├── name_loc: (7,8)-(7,9) = "f"
126+
│ ├── receiver: ∅
127+
│ ├── parameters: ∅
128+
│ ├── body:
129+
│ │ @ StatementsNode (location: (7,12)-(7,15))
130+
│ │ ├── flags: ∅
131+
│ │ └── body: (length: 1)
132+
│ │ └── @ CallNode (location: (7,12)-(7,15))
133+
│ │ ├── flags: ignore_visibility
134+
│ │ ├── receiver: ∅
135+
│ │ ├── call_operator_loc: ∅
136+
│ │ ├── name: :p
137+
│ │ ├── message_loc: (7,12)-(7,13) = "p"
138+
│ │ ├── opening_loc: ∅
139+
│ │ ├── arguments:
140+
│ │ │ @ ArgumentsNode (location: (7,14)-(7,15))
141+
│ │ │ ├── flags: ∅
142+
│ │ │ └── arguments: (length: 1)
143+
│ │ │ └── @ IntegerNode (location: (7,14)-(7,15))
144+
│ │ │ ├── flags: static_literal, decimal
145+
│ │ │ └── value: 1
146+
│ │ ├── closing_loc: ∅
147+
│ │ └── block: ∅
148+
│ ├── locals: []
149+
│ ├── def_keyword_loc: (7,4)-(7,7) = "def"
150+
│ ├── operator_loc: ∅
151+
│ ├── lparen_loc: ∅
152+
│ ├── rparen_loc: ∅
153+
│ ├── equal_loc: (7,10)-(7,11) = "="
154+
│ └── end_keyword_loc: ∅
155+
└── operator_loc: (7,2)-(7,3) = "="

src/prism.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19505,7 +19505,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1950519505
pm_do_loop_stack_push(parser, false);
1950619506
statements = (pm_node_t *) pm_statements_node_create(parser);
1950719507

19508-
pm_node_t *statement = parse_expression(parser, PM_BINDING_POWER_DEFINED + 1, binding_power < PM_BINDING_POWER_COMPOSITION, false, PM_ERR_DEF_ENDLESS, (uint16_t) (depth + 1));
19508+
// In endless method bodies, we need to handle command calls carefully.
19509+
// We want to allow command calls in assignment context but maintain
19510+
// the same binding power to avoid changing how operators are parsed.
19511+
// Note that we're intentionally NOT allowing code like `private def foo = puts "Hello"`
19512+
// because the original parser, parse.y, can't handle it and we want to maintain the same behavior
19513+
bool allow_command_call = (binding_power == PM_BINDING_POWER_ASSIGNMENT) ||
19514+
(binding_power < PM_BINDING_POWER_COMPOSITION);
19515+
19516+
pm_node_t *statement = parse_expression(parser, PM_BINDING_POWER_DEFINED + 1, allow_command_call, false, PM_ERR_DEF_ENDLESS, (uint16_t) (depth + 1));
1950919517

1951019518
if (accept1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) {
1951119519
context_push(parser, PM_CONTEXT_RESCUE_MODIFIER);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
private def foo = puts "Hello"
2+
^ unexpected string literal, expecting end-of-input
3+

test/prism/fixtures/endless_methods.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ def foo = 1
33
def bar = A ""
44

55
def method = 1 + 2 + 3
6+
7+
x = def f = p 1

0 commit comments

Comments
 (0)