[ruby/prism] Support itblock for Prism::Translation::Parser

## Summary

`itblock` node is added to support the `it` block parameter syntax introduced in Ruby 3.4.

```console
$ ruby -Ilib -rprism -rprism/translation/parser34 -e 'buffer = Parser::Source::Buffer.new("path"); buffer.source = "proc { it }"; \
                                                      p Prism::Translation::Parser34.new.tokenize(buffer)[0]'
s(:itblock,
  s(:send, nil, :proc), :it,
  s(:lvar, :it))
```

This node design is similar to the `numblock` node, which was introduced for the numbered parameter syntax in Ruby 2.7.

```
$ ruby -Ilib -rprism -rprism/translation/parser34 -e 'buffer = Parser::Source::Buffer.new("path"); buffer.source = "proc { _1 }"; \
                                                      p Prism::Translation::Parser34.new.tokenize(buffer)[0]'
s(:numblock,
  s(:send, nil, :proc), 1,
  s(:lvar, :_1))
```

The difference is that while numbered parameters can have multiple parameters, the `it` block parameter syntax allows only a single parameter.

In Ruby 3.3, the conventional node prior to the `it` block parameter syntax is returned.

```console
$ ruby -Ilib -rprism -rprism/translation/parser33 -e 'buffer = Parser::Source::Buffer.new("path"); buffer.source = "proc { it }"; \
                                                      p Prism::Translation::Parser33.new.tokenize(buffer)[0]'
s(:block,
  s(:send, nil, :proc),
  s(:args),
  s(:send, nil, :it))
```

## Development Note

The Parser gem does not yet support the `it` block parameter syntax. This is the first case where Prism's node design precedes that of the Parser gem.
When implementing https://github.com/whitequark/parser/issues/962, this node design will need to be taken into consideration.

https://github.com/ruby/prism/commit/c141e1420a
This commit is contained in:
Koichi ITO 2025-02-26 13:02:17 +09:00 committed by git
parent 97c133a859
commit 6b4453e332
3 changed files with 70 additions and 1 deletions

View File

@ -6,7 +6,55 @@ module Prism
# A builder that knows how to convert more modern Ruby syntax
# into whitequark/parser gem's syntax tree.
class Builder < ::Parser::Builders::Default
# It represents the `it` block argument, which is not yet implemented in the Parser gem.
def itarg
n(:itarg, [:it], nil)
end
# The following three lines have been added to support the `it` block parameter syntax in the source code below.
#
# if args.type == :itarg
# block_type = :itblock
# args = :it
#
# https://github.com/whitequark/parser/blob/v3.3.7.1/lib/parser/builders/default.rb#L1122-L1155
def block(method_call, begin_t, args, body, end_t)
_receiver, _selector, *call_args = *method_call
if method_call.type == :yield
diagnostic :error, :block_given_to_yield, nil, method_call.loc.keyword, [loc(begin_t)]
end
last_arg = call_args.last
if last_arg && (last_arg.type == :block_pass || last_arg.type == :forwarded_args)
diagnostic :error, :block_and_blockarg, nil, last_arg.loc.expression, [loc(begin_t)]
end
if args.type == :itarg
block_type = :itblock
args = :it
elsif args.type == :numargs
block_type = :numblock
args = args.children[0]
else
block_type = :block
end
if [:send, :csend, :index, :super, :zsuper, :lambda].include?(method_call.type)
n(block_type, [ method_call, args, body ],
block_map(method_call.loc.expression, begin_t, end_t))
else
# Code like "return foo 1 do end" is reduced in a weird sequence.
# Here, method_call is actually (return).
actual_send, = *method_call
block =
n(block_type, [ actual_send, args, body ],
block_map(actual_send.loc.expression, begin_t, end_t))
n(method_call.type, [ block ],
method_call.loc.with_expression(join_exprs(method_call, block)))
end
end
end
end
end

View File

@ -1138,7 +1138,7 @@ module Prism
# -> { it }
# ^^^^^^^^^
def visit_it_parameters_node(node)
builder.args(nil, [], nil, false)
builder.itarg
end
# foo(bar: baz)

View File

@ -6,6 +6,7 @@ begin
verbose, $VERBOSE = $VERBOSE, nil
require "parser/ruby33"
require "prism/translation/parser33"
require "prism/translation/parser34"
rescue LoadError
# In CRuby's CI, we're not going to test against the parser gem because we
# don't want to have to install it. So in this case we'll just skip this test.
@ -148,6 +149,22 @@ module Prism
end
end
def test_it_block_parameter_syntax
it_fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures/it.txt")
buffer = Parser::Source::Buffer.new(it_fixture_path)
buffer.source = it_fixture_path.read
actual_ast = Prism::Translation::Parser34.new.tokenize(buffer)[0]
it_block_parameter_sexp = parse_sexp {
s(:itblock,
s(:send, nil, :x), :it,
s(:lvar, :it))
}
assert_equal(it_block_parameter_sexp, actual_ast.to_sexp)
end
private
def assert_equal_parses(fixture, compare_asts: true, compare_tokens: true, compare_comments: true)
@ -246,5 +263,9 @@ module Prism
"actual: #{actual_comments.inspect}"
}
end
def parse_sexp(&block)
Class.new { extend AST::Sexp }.instance_eval(&block).to_sexp
end
end
end