Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

+ Added emit_forward_arg compatibility flag. #710

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -29,6 +29,7 @@ below for explanation of `emit_*` calls):
Parser::Builders::Default.emit_encoding = true
Parser::Builders::Default.emit_index = true
Parser::Builders::Default.emit_arg_inside_procarg0 = true
Parser::Builders::Default.emit_forward_arg = true

Parse a chunk of code:

Expand Down
23 changes: 21 additions & 2 deletions doc/AST_FORMAT.md
Expand Up @@ -1164,13 +1164,13 @@ s(:numblock,

## Forward arguments

### Method definition accepting forwarding arguments
### Method definition accepting only forwarding arguments

Ruby 2.7 introduced a feature called "arguments forwarding".
When a method takes any arguments for forwarding them in the future
the whole `args` node gets replaced with `forward-args` node.

Format:
Format if `emit_forward_arg` compatibility flag is disabled:

~~~
(def :foo
Expand All @@ -1181,6 +1181,25 @@ Format:
~~~~~ expression
~~~

However, Ruby 2.8 added support for leading arguments before `...`, and so
it can't be used as a replacement of the `(args)` node anymore. To solve it
`emit_forward_arg` should be enabled.

Format if `emit_forward_arg` compatibility flag is enabled:

~~~
(def :foo
(args
(forward-arg)) nil)
"def foo(...); end"
~ begin (args)
~ end (args)
~~~~~ expression (args)
~~~ expression (forward_arg)
~~~

Note that the node is called `forward_arg` when emitted separately.

### Method call taking arguments of the currently forwarding method

Format:
Expand Down
1 change: 1 addition & 0 deletions lib/parser/ast/processor.rb
Expand Up @@ -127,6 +127,7 @@ def process_argument_node(node)
alias on_kwarg process_argument_node
alias on_kwoptarg process_argument_node
alias on_kwrestarg process_argument_node
alias on_forward_arg process_argument_node

def on_procarg0(node)
if node.children[0].is_a?(Symbol)
Expand Down
44 changes: 41 additions & 3 deletions lib/parser/builders/default.rb
Expand Up @@ -80,6 +80,8 @@ class << self
attr_accessor :emit_index
end

@emit_index = false

class << self
##
# AST compatibility attribute; causes a single non-mlhs
Expand All @@ -95,7 +97,36 @@ class << self
attr_accessor :emit_arg_inside_procarg0
end

@emit_index = false
@emit_arg_inside_procarg0 = false

class << self
##
# AST compatibility attribute; arguments forwarding initially
# didn't have support for leading arguments
# (i.e. `def m(a, ...); end` was a syntax error). However, Ruby 2.8
# added support for any number of arguments in front of the `...`.
#
# If set to false (the default):
# 1. `def m(...) end` is emitted as
# s(:def, :m, s(:forward_args), nil)
# 2. `def m(a, b, ...) end` is emitted as
# s(:def, :m,
# s(:args, s(:arg, :a), s(:arg, :b), s(:forward_arg)))
#
# If set to true it uses a single format:
# 1. `def m(...) end` is emitted as
# s(:def, :m, s(:args, s(:forward_arg)))
# 2. `def m(a, b, ...) end` is emitted as
# s(:def, :m, s(:args, s(:arg, :a), s(:arg, :b), s(:forward_arg)))
#
# It does't matter that much on 2.7 (because there can't be any leading arguments),
# but on 2.8 it should be better enabled to use a single AST format.
#
# @return [Boolean]
attr_accessor :emit_forward_arg
end

@emit_forward_arg = false

class << self
##
Expand All @@ -106,6 +137,7 @@ def modernize
@emit_encoding = true
@emit_index = true
@emit_arg_inside_procarg0 = true
@emit_forward_arg = true
end
end

Expand Down Expand Up @@ -709,8 +741,14 @@ def numargs(max_numparam)
n(:numargs, [ max_numparam ], nil)
end

def forward_args(begin_t, dots_t, end_t)
n(:forward_args, [], collection_map(begin_t, token_map(dots_t), end_t))
def forward_only_args(begin_t, dots_t, end_t)
if self.class.emit_forward_arg
forward_arg = n(:forward_arg, [], token_map(dots_t))
n(:args, [ forward_arg ],
collection_map(begin_t, [ forward_arg ], end_t))
else
n(:forward_args, [], collection_map(begin_t, token_map(dots_t), end_t))
end
end

def arg(name_t)
Expand Down
2 changes: 1 addition & 1 deletion lib/parser/meta.rb
Expand Up @@ -26,7 +26,7 @@ module class sclass def defs def_e defs_e undef alias args
ident root lambda indexasgn index procarg0
restarg_expr blockarg_expr
objc_kwarg objc_restarg objc_varargs
numargs numblock forward_args forwarded_args
numargs numblock forward_args forwarded_args forward_arg
case_match in_match in_pattern
match_var pin match_alt match_as match_rest
array_pattern match_with_trailing_comma array_pattern_with_tail
Expand Down
2 changes: 1 addition & 1 deletion lib/parser/ruby27.y
Expand Up @@ -2532,7 +2532,7 @@ keyword_variable: kNIL
}
| tLPAREN2 args_forward rparen
{
result = @builder.forward_args(val[0], val[1], val[2])
result = @builder.forward_only_args(val[0], val[1], val[2])
@static_env.declare_forward_args

@lexer.state = :expr_value
Expand Down
2 changes: 1 addition & 1 deletion lib/parser/ruby28.y
Expand Up @@ -2595,7 +2595,7 @@ keyword_variable: kNIL
}
| tLPAREN2 args_forward rparen
{
result = @builder.forward_args(val[0], val[1], val[2])
result = @builder.forward_only_args(val[0], val[1], val[2])
@static_env.declare_forward_args

@lexer.state = :expr_value
Expand Down
2 changes: 1 addition & 1 deletion lib/parser/runner.rb
Expand Up @@ -37,7 +37,7 @@ def execute(options)

private

LEGACY_MODES = %i[lambda procarg0 encoding index arg_inside_procarg0].freeze
LEGACY_MODES = %i[lambda procarg0 encoding index arg_inside_procarg0 forward_arg].freeze

def runner_name
raise NotImplementedError, "implement #{self.class}##{__callee__}"
Expand Down
27 changes: 26 additions & 1 deletion test/test_parser.rb
Expand Up @@ -7811,7 +7811,8 @@ def test_circular_argument_reference_error
end
end

def test_forward_args
def test_forward_args_legacy
Parser::Builders::Default.emit_forward_arg = false
assert_parses(
s(:def, :foo,
s(:forward_args),
Expand Down Expand Up @@ -7843,7 +7844,27 @@ def test_forward_args
%q{def foo(...); end},
%q{},
SINCE_2_7)
ensure
Parser::Builders::Default.emit_forward_arg = true
end

def test_forward_arg
assert_parses(
s(:def, :foo,
s(:args,
s(:forward_arg)),
s(:send, nil, :bar,
s(:forwarded_args))),
%q{def foo(...); bar(...); end},
%q{ ~ begin (args)
| ~~~~~ expression (args)
| ~ end (args)
| ~~~ expression (args.forward_arg)
| ~~~ expression (send.forwarded_args)},
SINCE_2_7)
end

def test_forward_args_invalid
assert_diagnoses(
[:error, :block_and_blockarg],
%q{def foo(...) bar(...) { }; end},
Expand Down Expand Up @@ -9556,7 +9577,10 @@ def test_endless_method
| ^ assignment
|~~~~~~~~~~~~~~~~~~~~~~ expression},
SINCE_2_8)
end

def test_endless_method_forwarded_args_legacy
Parser::Builders::Default.emit_forward_arg = false
assert_parses(
s(:def_e, :foo,
s(:forward_args),
Expand All @@ -9568,6 +9592,7 @@ def test_endless_method
| ^ assignment
|~~~~~~~~~~~~~~~~~~~~~~~ expression},
SINCE_2_8)
Parser::Builders::Default.emit_forward_arg = true
end

def test_endless_method_without_brackets
Expand Down