Skip to content

Commit c58deff

Browse files
committed
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 whitequark/parser#962, this node design will need to be taken into consideration.
1 parent ad7d82c commit c58deff

File tree

7 files changed

+239
-5
lines changed

7 files changed

+239
-5
lines changed

lib/prism/translation/parser/builder.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,55 @@ class Parser
66
# A builder that knows how to convert more modern Ruby syntax
77
# into whitequark/parser gem's syntax tree.
88
class Builder < ::Parser::Builders::Default
9+
# It represents the `it` block argument, which is not yet implemented in the Parser gem.
10+
def itarg
11+
n(:itarg, [:it], nil)
12+
end
913

14+
# The following three lines have been added to support the `it` block parameter syntax in the source code below.
15+
#
16+
# if args.type == :itarg
17+
# block_type = :itblock
18+
# args = :it
19+
#
20+
# https://github.com/whitequark/parser/blob/v3.3.7.1/lib/parser/builders/default.rb#L1122-L1155
21+
def block(method_call, begin_t, args, body, end_t)
22+
_receiver, _selector, *call_args = *method_call
23+
24+
if method_call.type == :yield
25+
diagnostic :error, :block_given_to_yield, nil, method_call.loc.keyword, [loc(begin_t)]
26+
end
27+
28+
last_arg = call_args.last
29+
if last_arg && (last_arg.type == :block_pass || last_arg.type == :forwarded_args)
30+
diagnostic :error, :block_and_blockarg, nil, last_arg.loc.expression, [loc(begin_t)]
31+
end
32+
33+
if args.type == :itarg
34+
block_type = :itblock
35+
args = :it
36+
elsif args.type == :numargs
37+
block_type = :numblock
38+
args = args.children[0]
39+
else
40+
block_type = :block
41+
end
42+
43+
if [:send, :csend, :index, :super, :zsuper, :lambda].include?(method_call.type)
44+
n(block_type, [ method_call, args, body ],
45+
block_map(method_call.loc.expression, begin_t, end_t))
46+
else
47+
# Code like "return foo 1 do end" is reduced in a weird sequence.
48+
# Here, method_call is actually (return).
49+
actual_send, = *method_call
50+
block =
51+
n(block_type, [ actual_send, args, body ],
52+
block_map(actual_send.loc.expression, begin_t, end_t))
53+
54+
n(method_call.type, [ block ],
55+
method_call.loc.with_expression(join_exprs(method_call, block)))
56+
end
57+
end
1058
end
1159
end
1260
end

lib/prism/translation/parser/compiler.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1157,7 +1157,7 @@ def visit_it_local_variable_read_node(node)
11571157
# -> { it }
11581158
# ^^^^^^^^^
11591159
def visit_it_parameters_node(node)
1160-
builder.args(nil, [], nil, false)
1160+
builder.itarg
11611161
end
11621162

11631163
# foo(bar: baz)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-> do it + 42 end
2+
3+
-> { it + 42 }
4+
5+
m do it + 42 end
6+
7+
m { it + 42 }

test/prism/ruby/parser_test.rb

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
verbose, $VERBOSE = $VERBOSE, nil
77
require "parser/ruby33"
88
require "prism/translation/parser33"
9+
require "parser/ruby34"
10+
require "prism/translation/parser34"
911
rescue LoadError
1012
# In CRuby's CI, we're not going to test against the parser gem because we
1113
# don't want to have to install it. So in this case we'll just skip this test.
@@ -55,6 +57,12 @@ def ==(other)
5557

5658
module Prism
5759
class ParserTest < TestCase
60+
IT_BLOCK_PARAMETER_FIXTURES = [
61+
'whitequark/it_arg_after_34.txt'
62+
]
63+
64+
RUBY34_FIXTURES = IT_BLOCK_PARAMETER_FIXTURES
65+
5866
# These files contain code that is being parsed incorrectly by the parser
5967
# gem, and therefore we don't want to compare against our translation.
6068
skip_incorrect = [
@@ -86,6 +94,10 @@ class ParserTest < TestCase
8694
# Regex with \c escape
8795
"unescaping.txt",
8896
"seattlerb/regexp_esc_C_slash.txt",
97+
98+
# Please remove this once the Parser gem supports the `it` block parameter syntax:
99+
# https://github.com/whitequark/parser/issues/962
100+
"whitequark/it_arg_after_34.txt"
89101
]
90102

91103
# These files are either failing to parse or failing to translate, so we'll
@@ -150,7 +162,13 @@ def assert_equal_parses(fixture, compare_asts: true, compare_tokens: true, compa
150162
buffer = Parser::Source::Buffer.new(fixture.path, 1)
151163
buffer.source = fixture.read
152164

153-
parser = Parser::Ruby33.new
165+
whitequark_parser, translation_parser = if RUBY34_FIXTURES.include?(fixture.path)
166+
[Parser::Ruby34, Prism::Translation::Parser34]
167+
else
168+
[Parser::Ruby33, Prism::Translation::Parser33]
169+
end
170+
171+
parser = whitequark_parser.new
154172
parser.diagnostics.consumer = ->(*) {}
155173
parser.diagnostics.all_errors_are_fatal = true
156174

@@ -161,8 +179,7 @@ def assert_equal_parses(fixture, compare_asts: true, compare_tokens: true, compa
161179
return
162180
end
163181

164-
actual_ast, actual_comments, actual_tokens =
165-
ignore_warnings { Prism::Translation::Parser33.new.tokenize(buffer) }
182+
actual_ast, actual_comments, actual_tokens = ignore_warnings { translation_parser.new.tokenize(buffer) }
166183

167184
if expected_ast == actual_ast
168185
if !compare_asts && !Fixture.custom_base_path?
@@ -183,6 +200,8 @@ def assert_equal_parses(fixture, compare_asts: true, compare_tokens: true, compa
183200
elsif compare_asts
184201
assert_equal expected_ast, actual_ast, -> { assert_equal_asts_message(expected_ast, actual_ast) }
185202
end
203+
204+
assert_prism_only_node(fixture.path, actual_ast)
186205
end
187206

188207
def assert_equal_asts_message(expected_ast, actual_ast)
@@ -242,5 +261,11 @@ def assert_equal_comments(expected_comments, actual_comments)
242261
"actual: #{actual_comments.inspect}"
243262
}
244263
end
264+
265+
def assert_prism_only_node(fixture_path, actual_ast)
266+
if IT_BLOCK_PARAMETER_FIXTURES.include?(fixture_path)
267+
assert_equal :itblock, actual_ast.children.first.type
268+
end
269+
end
245270
end
246271
end

test/prism/ruby/ruby_parser_test.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ class RubyParserTest < TestCase
4545
"whitequark/pattern_matching_single_match.txt",
4646
"whitequark/ruby_bug_12402.txt",
4747
"whitequark/ruby_bug_14690.txt",
48-
"whitequark/space_args_block.txt"
48+
"whitequark/space_args_block.txt",
49+
50+
# Please remove this once the Parser gem supports the `it` block parameter syntax:
51+
# https://github.com/whitequark/parser/issues/962
52+
"whitequark/it_arg_after_34.txt"
4953
]
5054

5155
# https://github.com/seattlerb/ruby_parser/issues/344

test/prism/snapshots/whitequark/it_arg_after_34.txt

Lines changed: 149 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/prism/test_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def test_name
6565
def self.each(except: [], &block)
6666
glob_pattern = ENV.fetch("FOCUS") { custom_base_path? ? File.join("**", "*.rb") : File.join("**", "*.txt") }
6767
paths = Dir[glob_pattern, base: BASE] - except
68+
6869
paths.each { |path| yield Fixture.new(path) }
6970
end
7071

0 commit comments

Comments
 (0)