Skip to content

Parse parenthesized arguments after a command call with a block - #11

Open
tarebyte wants to merge 1 commit into
masterfrom
fix/block-arg-after-command-block-chain
Open

Parse parenthesized arguments after a command call with a block#11
tarebyte wants to merge 1 commit into
masterfrom
fix/block-arg-after-command-block-chain

Conversation

@tarebyte

@tarebyte tarebyte commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Summary

Chaining a call off a command call that carries a block already worked on master_expression includes _chained_command_call. But that rule is only receiver, operator, method, with no argument list. A ( after the chained method therefore could not be absorbed by the chain and instead completed a command_call, whose arguments are an unparenthesized command_argument_list, making the parentheses a parenthesized_statements argument. A block-pass argument then produced an ERROR:

foo bar do |x|
  x
end.each(&:sort)
    arguments: (argument_list
      (parenthesized_statements
        (ERROR)              <- the &
        (simple_symbol)))

a.each(&:sort), foo(bar).each(&:sort) and foo { }.each(&:sort) were all fine — only the do ... end command-call receiver was affected.

A command call carrying a block is not a _primary, so the chain cannot go through _call/call. This adds a mirrored pair of rules for such receivers: _chained_command_call_with_arguments adds a parenthesized argument list and/or a block, and _command_call_with_block_chain lets either it or the existing _chained_command_call act as the receiver of a further chained call. No new conflicts entries were needed.

Validation

  • 333 corpus parses and 5 highlight files, zero failures.
  • Parsing all 4,437 ruby/spec files with master and with this branch produces identical trees. This is a regression check only — no file in that corpus reaches the new rules, which is why the result is identical rather than merely equivalent.
  • Prism conformance: 4,431 valid files, 0 expected mismatches, 0 unexpected mismatches. That count excludes 6 of the 4,437 files — 2 that Prism itself rejects and 4 with unsupported encodings.
  • The new corpus cases are the real evidence, and their expected trees were checked against Prism rather than only against the parser's own output.
  • puts (1 + 2).to_s and foo (a), (b), where the parentheses really are a command-call argument, are unchanged.
  • Also fixed by the same change: end.size.upcase, end.each do ... end, end.each(1).size, end.each(&:sort).first.
  • Still not accepted, and pre-existing: end[0] and end.map(&:to_s) + z, which need the receiver to be a _primary.
  • src/parser.c grows 31,457,296 -> 32,058,713 bytes (+1.9%).

References

Found while checking tree-sitter#266, which reported the chaining itself. The chaining now works on master; this fixes the argument list that chain produces.

Note: this branch and #6 both regenerate src/parser.c, so whichever merges second will need a regenerate.

A call chained off a command call that carries a block, such as
`foo bar do |x| x end.each(&:sort)`, was reachable only through
`command_call`, whose arguments are an unparenthesized
`command_argument_list`. The `(` therefore started a
`parenthesized_statements` argument rather than the call's own
`argument_list`, so `&blk` and `&:sym` produced an ERROR node and a
trailing block was parsed as a hash.

A command call with a block is not a `_primary`, so such a receiver
cannot flow through `_call`/`call`. Mirror that pair for it:
`_chained_command_call_with_arguments` gives the chain a parenthesized
argument list and/or a block, and `_command_call_with_block_chain` lets
a chain act as the receiver of a further chained call, so
`end.map(&:to_s).join(", ")` and `end.size.upcase` also parse.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant