mbj / unparser

Turn Ruby AST into semantically equivalent Ruby source

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Keyword arguments in Ruby 3.0

camertron opened this issue · comments

First of all, thanks for unparser! As you know, it has been extremely useful for rux :)

Now, on to the issue! It looks like keyword arguments aren't properly handled in unparser >= v0.5.6. Consider this class:

class Foo
  def initialize(a:, c:)
  end
end

and this roundtrip through parser and unparser:

code = Unparser.unparse(Parser::CurrentRuby.parse("Foo.new(a: 'b', c: 'd')"))
# => Foo.new({ a: "b", c: "d" })

# in Ruby 2.7
eval(code)
# => #<Foo:0x00007fc86a0d7b88>

# in Ruby 3.0
eval(code)
# => ArgumentError (wrong number of arguments (given 1, expected 0; required keywords: a, c))

I think the problem comes from the extra curly braces unparser adds. I've tried adding a double splat, i.e. **, but unparser seems to wrap even that in an extra pair of curlies.

Switching to unparser <= 0.5.5 fixes the problem.

@camertron The issue is you are NOT passing the modern AST format as clearly stated in unparsers readme (no pun intend). Check this out:

bundle exec unparser --verbose x.rb
Original-Source:
class Foo
  def initialize(a:, c:)
  end
end

Foo.new(a: 'b', c: 'd')

Generated-Source:
class Foo
  def initialize(a:, c:)
  end
end
Foo.new(a: "b", c: "d")

Original-Node:
(begin
  (class
    (const nil :Foo) nil
    (def :initialize
      (args
        (kwarg :a)
        (kwarg :c)) nil))
  (send
    (const nil :Foo) :new
    (kwargs
      (pair
        (sym :a)
        (str "b"))
      (pair
        (sym :c)
        (str "d")))))
Generated-Node:
(begin
  (class
    (const nil :Foo) nil
    (def :initialize
      (args
        (kwarg :a)
        (kwarg :c)) nil))
  (send
    (const nil :Foo) :new
    (kwargs
      (pair
        (sym :a)
        (str "b"))
      (pair
        (sym :c)
        (str "d")))))
Success: x.rb

If I run this modified example:

require 'unparser'

class Foo
  def initialize(a:, c:)
  end
end

p eval(Unparser.unparse(Unparser.parse("Foo.new(a: 'b', c: 'd')")))

It passes correctly. Your generated AST needs to emit what ruby would parse in the modern format. Making unparser support all the legacy formats (and the combinations) is impossible at my time budget.

I'm closing this as I assume the issue is your side. If you can provide a case where bundle exec unparser some_ruby_code.rb fails: Its in scope of unparser.

For your case its likely that you need to emit the modern AST nodes and it will work.

Feel free to discuss or reopen if you think my assessment is wrong.

Ahhhh ok that makes sense! Thanks for pointing me in the right direction. I decided to opt-in to the modern AST format and everything is working as expected 👍

Thanks!