Skip to content

Fix invalid sig generated for anonymous block param (&)#935

Open
prattik-wav wants to merge 5 commits into
Shopify:mainfrom
prattik-wav:fix-anonymous-block-param-sig
Open

Fix invalid sig generated for anonymous block param (&)#935
prattik-wav wants to merge 5 commits into
Shopify:mainfrom
prattik-wav:fix-anonymous-block-param-sig

Conversation

@prattik-wav

Copy link
Copy Markdown

Fixes #928

Problem

rbs_comments_to_sorbet_sigs generates invalid Ruby when a method uses Ruby 3.1+ anonymous block forwarding (def foo(&); end). The generated sig contains &block: inside params(), which is a syntax error:

# Input
#: (String) ?{ (String) -> void } -> String
def bar(request, &); end

# Generated (broken)
sig { params(request: String, &block: ::T.nilable(::T.proc.params(arg0: String).void)).returns(String) }
def bar(request, &); end

Named block params (&block) were already translated correctly.

Root Cause

Prism::BlockParameterNode#name is nil for anonymous &. The RBI::RBS::MethodTypeTranslator doesn't distinguish this case and falls back to the default name &block, which renders as &block: inside params() — a syntax error because & is not valid in a keyword argument position.

Fix

After translator.result produces the sig, check whether the original def_node has an anonymous block parameter (a BlockParameterNode with nil name). If so, strip the leading & from any block param name in the generated sig, producing the valid block: form.

# Generated (fixed)
sig { params(request: String, block: ::T.nilable(::T.proc.params(arg0: String).void)).returns(String) }
def bar(request, &); end

Testing

  • Added test_translate_to_rbi_anonymous_block_param — covers the broken case from the issue
  • Added test_translate_to_rbi_named_block_param_unchanged — regression guard confirming named &block still works
  • Verified the fixed output compiles cleanly with RubyVM::InstructionSequence.compile

Fixes #928

When a method uses Ruby 3.1+ anonymous block forwarding (def foo(&); end),
rbs_comments_to_sorbet_sigs was generating `&block:` inside params(), which
is a syntax error. Named block params (`&block`) were already handled correctly.

Detect a nil-named BlockParameterNode in the def and strip the leading `&`
from any block param name in the translated sig.

Fixes Shopify#928
@prattik-wav prattik-wav requested a review from a team as a code owner June 1, 2026 16:22
Comment on lines +906 to +907
sig { params(request: String, block: ::T.nilable(::T.proc.params(arg0: String).void)).returns(String) }
def bar(request, &); end

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.

Comment on lines +191 to +195
if anonymous_block_param?(def_node)
sig.params.each do |param|
param.name = param.name.delete_prefix("&") if param.name.start_with?("&")
end
end

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 am not sure if this is the correct place to fix this problem. We need to solve it at the source.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah the correct place to fix this is in the RBI gem that Spoom uses for this translation:

https://github.com/Shopify/rbi/blob/4a4f30b9ca743f7723ce451dc06a9b7cf62a42b0/lib/rbi/rbs/method_type_translator.rb#L46

Comment on lines +910 to +912

# Must also be valid Ruby
assert RubyVM::InstructionSequence.compile(res)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is overkill, we don't need to waste time compiling it. Perhaps assert Prism.parse_success?(res) at most, but we match the exact expected source we want, so there's no need to confirm this here

Suggested change
# Must also be valid Ruby
assert RubyVM::InstructionSequence.compile(res)

Comment on lines +191 to +195
if anonymous_block_param?(def_node)
sig.params.each do |param|
param.name = param.name.delete_prefix("&") if param.name.start_with?("&")
end
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah the correct place to fix this is in the RBI gem that Spoom uses for this translation:

https://github.com/Shopify/rbi/blob/4a4f30b9ca743f7723ce451dc06a9b7cf62a42b0/lib/rbi/rbs/method_type_translator.rb#L46

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RBS Rewriting produces invalid Ruby for anonymous block parameters

3 participants