Skip to content

Parse proc call shorthand .() and ::() after a command with a do block - #9793

Open
edipofederle wants to merge 1 commit into
jruby:masterfrom
edipofederle:fix-9270-proc-call-shorthand-after-command-block
Open

edipofederle wants to merge 1 commit into
jruby:masterfrom
edipofederle:fix-9270-proc-call-shorthand-after-command-block

Conversation

@edipofederle

@edipofederle edipofederle commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #9270: the proc-call shorthand .() / ::() now parses when it follows a paren-less command call that carries a do...end block.

Problem

o = Object.new
def o.m(*arg); proc {|a| arg.join + a }; end
o.m "foo", "bar" do end.("buz")    # was: SyntaxError: unexpected '('
o.m "foo", "bar" do end::("buz")   # was: SyntaxError: unexpected '('

o.m "foo", "bar" do end parses as block_call : command do_block. block_call could chain a named method (.foo / ::foo) but had no production for the call shorthand .() / ::(). That shorthand lived only under method_call, which requires a primary_value receiver, and a block_call is not one, so the (
after ./:: had no matching rule.

Fix

Add two productions to block_call in RubyParser.y, mirroring the existing method_call shorthand (JRuby splits call_op vs tCOLON2 because new_call has distinct overloads). This matches CRuby's block_call call_op2 paren_args in parse.y. RubyParser.java and RipperParser.java were regenerated with jay; no new grammar conflicts were introduced.

Testing

  • test/mri/ruby/test_parse.rb test_call_command (the CRuby regression test for this exact case) now passes; its entry in test/mri/excludes/TestParse.rb is removed.
  • Baseline comparison on the full test_parse.rb: failures drop by exactly one (the fixed test); no new failures or errors.
  • test/mri/ripper/test_ripper.rb results are unchanged (RipperParser regeneration introduced no regression).
  • Both reported forms now evaluate to "foobarbuz"; named-method chaining after a do-block and .()/::()/&.() on ordinary receivers continue to work.

A paren-less command call carrying a do...end block parses as
block_call : command do_block. block_call could chain a named method
(.foo / ::foo) but had no production for the call shorthand .() / ::(),
which only existed under method_call (requiring a primary_value receiver).
So 'o.m "foo" do end.("buz")' raised a syntax error on the '('.

Add two block_call productions mirroring the method_call shorthand
(split call_op vs tCOLON2 to match new_call's overloads), matching
CRuby's 'block_call call_op2 paren_args'. Regenerated RubyParser and
RipperParser via jay; no new grammar conflicts.

Fixes the previously-excluded MRI test_parse.rb test_call_command.

Closes jruby#9270
@edipofederle

Copy link
Copy Markdown
Contributor Author

@headius could you take a look when possible? Thanks

@headius headius left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably good to go but I could not correlate all changes with CRuby's parse.y. We'll give @enebo a chance to respond but this can probably merge any time.

/*% %*/
/*% ripper: method_add_block!(command_call!($1, $2, $3, $4), $5) %*/
}
| block_call call_op paren_args {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/*% %*/
/*% ripper: method_add_arg!(call!($1, $2, ID2VAL(idCall)), $3) %*/
}
| block_call tCOLON2 paren_args {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not locate this production in parse.y. Maybe @enebo can have a look or we just go with it since it's working.

@headius headius added this to the JRuby 10.1.3.0 milestone Oct 15, 2026
@enebo

enebo commented Oct 15, 2026

Copy link
Copy Markdown
Member

I think you should replace the first production line with:

                | block_call call_op2 paren_args

Notice the call_op2 vs call_op. I think with what you can lazy operator calling would still be broken. Delete the second production:

| block_call tCOLON2 paren_args {

That is already covered by call_op2 once you change the first production line.

@enebo

enebo commented Oct 15, 2026

Copy link
Copy Markdown
Member

@edipofederle In addition to the above did you run ./tool/generate_parser to make those .java files? It shows a conflict and unless you made this before sferik's recent changes that should not happen.

@enebo enebo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the changes I requested and it should be good to go. I find the lack of safe merging weird. There should be no conflict and yet I see sferik as your last commit to the RubyParser.java file. Perhaps there was a followup commit from that work?

/*% %*/
/*% ripper: method_add_block!(command_call!($1, $2, $3, $4), $5) %*/
}
| block_call call_op paren_args {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to call_op2

| block_call call_op paren_args {
/*%%%*/
$$ = p.new_call($1, $2, LexingCommon.CALL, $3, null, @3.start());
p.safe_navigation_end($<Node>$, @3.end, @3.end, true); // o.m foo do end.(): the parentheses are the message

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this comment. At a minimum it is a confusing example since the block is on 'foo' and not 'o.m'.

/*% %*/
/*% ripper: method_add_arg!(call!($1, $2, ID2VAL(idCall)), $3) %*/
}
| block_call tCOLON2 paren_args {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this whole production. Using call_op2 fixes removes the need for this.

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.

Proc call shorthand fails to parse with inline args and block

3 participants