Reputation: 11
Using Ruby 2.6.3, I'm coming across a bug where an ArgumentError is thrown when I try to 'warn' a hash that has at least one key as a symbol. If I try to 'puts' the same hash, there is no error and the hash is printed normally.
>> h = {:one => 1}
=> {:one=>1}
>> puts h
{:one=>1}
=> nil
>> warn h
Traceback (most recent call last):
5: from /Users/rishi/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `<main>'
4: from /Users/rishi/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `load'
3: from /Users/rishi/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
2: from (irb):48
1: from /Users/rishi/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_warn.rb:15:in `block in <module:Kernel>'
ArgumentError (unknown keyword: one)
It seems to be throwing the error at line 15 of my kernel_warn.rb:
module_function define_method(:warn) {|*messages, uplevel: nil|
...
Can anyone advise on how to fix?
Upvotes: 1
Views: 127
Reputation: 198368
Since like forever, Ruby allowed to drop the curly braces around hash-as-last-argument in method calls to allow for keyword-like arguments. This allowed Rails to do e.g.
link_to "Delete", @item, method: "delete"
which is interpreted as
link_to("Delete", @item, { method: "delete" })
Then Ruby got support for keyword arguments, but they had to be hacked in, in a way that will still make all the old code running. So keyword arguments are also treated as if they were this argument-final hash. The problem for you is this: if there are keyword arguments in the signature, then the keyword arguments are strictly checked against the signature; you can't have extra keyword arguments, just like you can't have extra positional arguments. Remember, these are all equivalent:
warn h
warn({ one: 1 })
warn(one: 1)
Ruby sees a positional argument in the signature, and takes a look if the last argument that was passed is a hash. It is, so it assumes it's the keyword argument hash that it needs to compare to the signature - and one:
is not a valid keyword.
What to do? Make sure the hash is not the last argument. At a trivial level, you can do this:
warn h, "was my hash"
# {:one=>1}
# was my hash
The other thing you can do is to remember that warn
wants a string; if it doesn't get one, it will stringify the arguments. So you can do it preemptively:
warn h.to_s
# {:one=>1}
tl;dr: Ruby keyword arguments are weird.
Upvotes: 2