diff --git a/lib/syntax_tree/dsl.rb b/lib/syntax_tree/dsl.rb index 4506aa04..64215789 100644 --- a/lib/syntax_tree/dsl.rb +++ b/lib/syntax_tree/dsl.rb @@ -146,8 +146,13 @@ def Binary(left, operator, right) end # Create a new BlockVar node. - def BlockVar(params, locals) - BlockVar.new(params: params, locals: locals, location: Location.default) + def BlockVar(params, locals, pipe) + BlockVar.new( + params: params, + locals: locals, + location: Location.default, + pipe: pipe + ) end # Create a new BlockArg node. @@ -551,11 +556,6 @@ def Lambda(params, statements) ) end - # Create a new LambdaVar node. - def LambdaVar(params, locals) - LambdaVar.new(params: params, locals: locals, location: Location.default) - end - # Create a new LBrace node. def LBrace(value) LBrace.new(value: value, location: Location.default) diff --git a/lib/syntax_tree/field_visitor.rb b/lib/syntax_tree/field_visitor.rb index f5607c67..1a46250d 100644 --- a/lib/syntax_tree/field_visitor.rb +++ b/lib/syntax_tree/field_visitor.rb @@ -526,14 +526,6 @@ def visit_lambda(node) end end - def visit_lambda_var(node) - node(node, "lambda_var") do - field("params", node.params) - list("locals", node.locals) if node.locals.any? - comments(node) - end - end - def visit_lbrace(node) visit_token(node, "lbrace") end diff --git a/lib/syntax_tree/mutation_visitor.rb b/lib/syntax_tree/mutation_visitor.rb index 0b4b9357..508b1ea7 100644 --- a/lib/syntax_tree/mutation_visitor.rb +++ b/lib/syntax_tree/mutation_visitor.rb @@ -500,11 +500,6 @@ def visit_lambda(node) ) end - # Visit a LambdaVar node. - def visit_lambda_var(node) - node.copy(params: visit(node.params), locals: visit_all(node.locals)) - end - # Visit a LBrace node. def visit_lbrace(node) node.copy diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 54d132e6..40fc17b6 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -2131,13 +2131,18 @@ def ===(other) end end - # BlockVar represents the parameters being declared for a block. Effectively - # this node is everything contained within the pipes. This includes all of the - # various parameter types, as well as block-local variable declarations. + # BlockVar represents the parameters being declared for a block or lambda. + # This includes all of the various parameter types, as well as + # block-local/lambda-local variable declarations. # # method do |positional, optional = value, keyword:, █ local| # end # + # OR + # + # -> (positional, optional = value, keyword:, █ local) do + # end + # class BlockVar < Node # [Params] the parameters being declared with the block attr_reader :params @@ -2148,10 +2153,15 @@ class BlockVar < Node # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(params:, locals:, location:) + # [boolean] whether or not the variables are within pipes + attr_reader :pipe + alias pipe? pipe + + def initialize(params:, locals:, location:, pipe:) @params = params @locals = locals @location = location + @pipe = pipe @comments = [] end @@ -2163,12 +2173,13 @@ def child_nodes [params, *locals] end - def copy(params: nil, locals: nil, location: nil) + def copy(params: nil, locals: nil, location: nil, pipe: nil) node = BlockVar.new( params: params || self.params, locals: locals || self.locals, - location: location || self.location + location: location || self.location, + pipe: pipe || self.pipe ) node.comments.concat(comments.map(&:copy)) @@ -2178,7 +2189,13 @@ def copy(params: nil, locals: nil, location: nil) alias deconstruct child_nodes def deconstruct_keys(_keys) - { params: params, locals: locals, location: location, comments: comments } + { + params: params, + locals: locals, + location: location, + comments: comments, + pipe: pipe + } end # Within the pipes of the block declaration, we don't want any spaces. So @@ -2194,16 +2211,12 @@ def call(q) SEPARATOR = Separator.new.freeze def format(q) - q.text("|") - q.group do - q.remove_breaks(q.format(params)) + pipe? ? q.remove_breaks(q.format(params)) : q.format(params) - if locals.any? - q.text("; ") - q.seplist(locals, SEPARATOR) { |local| q.format(local) } - end + if locals.any? + q.text("; ") + q.seplist(locals, SEPARATOR) { |local| q.format(local) } end - q.text("|") end def ===(other) @@ -2211,6 +2224,10 @@ def ===(other) ArrayMatch.call(locals, other.locals) end + def empty? + params.empty? && locals.empty? + end + # When a single required parameter is declared for a block, it gets # automatically expanded if the values being yielded into it are an array. def arg0? @@ -4486,8 +4503,9 @@ def format_break(q, break_opening, break_closing) q.format(BlockOpenFormatter.new(break_opening, opening), stackable: false) if block_var - q.text(" ") - q.format(block_var) + q.text(" |") + q.group { q.format(block_var) } + q.text("|") end unless bodystmt.empty? @@ -4507,7 +4525,9 @@ def format_flat(q, flat_opening, flat_closing) if block_var q.breakable_space - q.format(block_var) + q.text("|") + q.group { q.format(block_var) } + q.text("|") q.breakable_space end @@ -7140,7 +7160,7 @@ def ===(other) # ->(value) { value * 2 } # class Lambda < Node - # [LambdaVar | Paren] the parameter declaration for this lambda + # [BlockVar | Paren] the parameter declaration for this lambda attr_reader :params # [BodyStmt | Statements] the expressions to be executed in this lambda @@ -7258,76 +7278,6 @@ def ===(other) end end - # LambdaVar represents the parameters being declared for a lambda. Effectively - # this node is everything contained within the parentheses. This includes all - # of the various parameter types, as well as block-local variable - # declarations. - # - # -> (positional, optional = value, keyword:, █ local) do - # end - # - class LambdaVar < Node - # [Params] the parameters being declared with the block - attr_reader :params - - # [Array[ Ident ]] the list of block-local variable declarations - attr_reader :locals - - # [Array[ Comment | EmbDoc ]] the comments attached to this node - attr_reader :comments - - def initialize(params:, locals:, location:) - @params = params - @locals = locals - @location = location - @comments = [] - end - - def accept(visitor) - visitor.visit_lambda_var(self) - end - - def child_nodes - [params, *locals] - end - - def copy(params: nil, locals: nil, location: nil) - node = - LambdaVar.new( - params: params || self.params, - locals: locals || self.locals, - location: location || self.location - ) - - node.comments.concat(comments.map(&:copy)) - node - end - - alias deconstruct child_nodes - - def deconstruct_keys(_keys) - { params: params, locals: locals, location: location, comments: comments } - end - - def empty? - params.empty? && locals.empty? - end - - def format(q) - q.format(params) - - if locals.any? - q.text("; ") - q.seplist(locals, BlockVar::SEPARATOR) { |local| q.format(local) } - end - end - - def ===(other) - other.is_a?(LambdaVar) && params === other.params && - ArrayMatch.call(locals, other.locals) - end - end - # LBrace represents the use of a left brace, i.e., {. class LBrace < Node # [String] the left brace diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index 825cd90e..486ad122 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -937,7 +937,8 @@ def on_block_var(params, locals) BlockVar.new( params: params, locals: locals || [], - location: beginning.location.to(ending.location) + location: beginning.location.to(ending.location), + pipe: true ) end @@ -2296,10 +2297,11 @@ def on_lambda(params, statements) Paren.new( lparen: params.lparen, contents: - LambdaVar.new( + BlockVar.new( params: params.contents, locals: locals, - location: location + location: location, + pipe: false ), location: params.location ) @@ -2324,8 +2326,13 @@ def on_lambda(params, statements) # In this case we've gotten to the plain set of parameters. In this # case there cannot be lambda locals, so we will wrap the parameters - # into a lambda var that has no locals. - LambdaVar.new(params: params, locals: [], location: params.location) + # into a block var that has no locals. + BlockVar.new( + params: params, + locals: [], + location: params.location, + pipe: false + ) end start_char = find_next_statement_start(opening.location.end_char) @@ -2345,12 +2352,17 @@ def on_lambda(params, statements) end # :call-seq: - # on_lambda_var: (Params params, Array[ Ident ] locals) -> LambdaVar + # on_lambda_var: (Params params, Array[ Ident ] locals) -> BlockVar def on_lambda_var(params, locals) location = params.location location = location.to(locals.last.location) if locals.any? - LambdaVar.new(params: params, locals: locals || [], location: location) + BlockVar.new( + params: params, + locals: locals || [], + location: location, + pipe: false + ) end # Ripper doesn't support capturing lambda local variables until 3.2. To diff --git a/lib/syntax_tree/translation/parser.rb b/lib/syntax_tree/translation/parser.rb index 8be4fc79..b316de9d 100644 --- a/lib/syntax_tree/translation/parser.rb +++ b/lib/syntax_tree/translation/parser.rb @@ -546,13 +546,13 @@ def visit_block_var(node) ) end - params = node.params children = - if ::Parser::Builders::Default.emit_procarg0 && node.arg0? + if ::Parser::Builders::Default.emit_procarg0 && node.arg0? && + node.pipe? # There is a special node type in the parser gem for when a single # required parameter to a block would potentially be expanded # automatically. We handle that case here. - required = params.requireds.first + required = node.params.requireds.first procarg0 = if ::Parser::Builders::Default.emit_arg_inside_procarg0 && required.is_a?(Ident) @@ -577,18 +577,23 @@ def visit_block_var(node) [procarg0] else - visit(params).children + visit(node.params).children end - s( - :args, - children + shadowargs, - smap_collection( - srange_length(node.start_char, 1), - srange_length(node.end_char, -1), - srange_node(node) - ) - ) + location = + if node.start_char == node.end_char + smap_collection_bare(nil) + elsif buffer.source[node.start_char - 1] == "(" + smap_collection( + srange_length(node.start_char, 1), + srange_length(node.end_char, -1), + srange_node(node) + ) + else + smap_collection_bare(srange_node(node)) + end + + s(:args, children + shadowargs, location) end # Visit a BodyStmt node. @@ -1520,7 +1525,7 @@ def visit_label(node) # Visit a Lambda node. def visit_lambda(node) args = - node.params.is_a?(LambdaVar) ? node.params : node.params.contents + node.params.is_a?(BlockVar) ? node.params : node.params.contents args_node = visit(args) type = :block @@ -1559,33 +1564,6 @@ def visit_lambda(node) ) end - # Visit a LambdaVar node. - def visit_lambda_var(node) - shadowargs = - node.locals.map do |local| - s( - :shadowarg, - [local.value.to_sym], - smap_variable(srange_node(local), srange_node(local)) - ) - end - - location = - if node.start_char == node.end_char - smap_collection_bare(nil) - elsif buffer.source[node.start_char - 1] == "(" - smap_collection( - srange_length(node.start_char, 1), - srange_length(node.end_char, -1), - srange_node(node) - ) - else - smap_collection_bare(srange_node(node)) - end - - s(:args, visit(node.params).children + shadowargs, location) - end - # Visit an MAssign node. def visit_massign(node) s( diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index eb57acd2..1c69e5a7 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -218,9 +218,6 @@ class Visitor < BasicVisitor # Visit a Lambda node. alias visit_lambda visit_child_nodes - # Visit a LambdaVar node. - alias visit_lambda_var visit_child_nodes - # Visit a LBrace node. alias visit_lbrace visit_child_nodes diff --git a/lib/syntax_tree/yarv/compiler.rb b/lib/syntax_tree/yarv/compiler.rb index 0f7e7372..5b14b93e 100644 --- a/lib/syntax_tree/yarv/compiler.rb +++ b/lib/syntax_tree/yarv/compiler.rb @@ -1103,10 +1103,6 @@ def visit_lambda(node) iseq.send(YARV.calldata(:lambda, 0, CallData::CALL_FCALL), lambda_iseq) end - def visit_lambda_var(node) - visit_block_var(node) - end - def visit_massign(node) visit(node.value) iseq.dup diff --git a/test/node_test.rb b/test/node_test.rb index 19fbeed2..88e7dcae 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -635,6 +635,25 @@ def test_lambda source = "->(value) { value * 2 }" assert_node(Lambda, source) + + at = location(chars: 3..8) + assert_node(BlockVar, source, at: at) { |node| node.params.contents } + end + + def test_lambda_no_parens + source = "-> value { value * 2 }" + + assert_node(Lambda, source) + + at = location(chars: 3..8) + assert_node(BlockVar, source, at: at, &:params) + end + + def test_lambda_braces + source = "lambda { |value| value * 2 }" + + at = location(chars: 9..16) + assert_node(BlockVar, source, at: at) { |node| node.block.block_var } end def test_lambda_do