Bug #14130
closedKeyword arguments are ripped from the middle of hash if argument have default value
Description
Here is the code:
def test1(source = {}, **opts)
puts "SOURCE: #{source}, OPTS: #{opts}"
end
def test2(source, **opts)
puts "SOURCE: #{source}, OPTS: #{opts}"
end
puts "No source"
test1(length: 2000)
# 1. SOURCE: {}, OPTS: {:length=>2000} -- OK, it is reasonable.
test2(length: 2000)
# 2. SOURCE: {:length=>2000}, OPTS: {} -- Exactly as expected.
puts
puts "Source is mixed hash"
test1('River name' => 'Mississippi', length: 2000, 'Country' => 'USA')
# 3. SOURCE: {"River name"=>"Mississippi", "Country"=>"USA"}, OPTS: {:length=>2000} -- It is already a bit weird
test2('River name' => 'Mississippi', length: 2000, 'Country' => 'USA')
# 4. SOURCE: {"River name"=>"Mississippi", :length=>2000, "Country"=>"USA"}, OPTS: {} -- The most weird thing!
My concern is cases (3) and (4). Ripping keyword argument from what from any logic looks like a middle of a hash (3) is already pretty strange. But the fact that this behavior depends on whether first argument has or has not default value (3 vs 4) clearly looks like some bug?
Checked on several recent versions, including ruby 2.5.0dev (2017-09-11 trunk 59836) [x86_64-linux]
(the last that is available on my RVM), behavior is consistent between them.
Updated by hsbt (Hiroshi SHIBATA) about 7 years ago
- Related to Feature #14183: "Real" keyword argument added
Updated by marcandre (Marc-Andre Lafortune) over 6 years ago
- Assignee set to nobu (Nobuyoshi Nakada)
This is a bug.
test1('River name' => 'Mississippi', length: 2000, 'Country' => 'USA')
# 3. SOURCE: {"River name"=>"Mississippi", "Country"=>"USA"}, OPTS: {:length=>2000} -- It is already a bit weird
My understanding is that under no circumstance should a mixed hash be split in two like this.
Ruby should recognize that some keys are no symbols and thus this hash can't be a keyword parameter hash and must be a positional argument. The correct result should thus be:
SOURCE: {"River name"=>"Mississippi", :length=>2000, "Country"=>"USA"}, OPTS: {}
Note that this should still be true even if the :length
key was the last one.
To obtain the current result, one would have to call test1({'River name' => 'Mississippi', 'Country' => 'USA'}, length: 2000)
Updated by jeremyevans0 (Jeremy Evans) over 5 years ago
With the changes in #14183, keyword splats support non-symbol keys, so with the master branch you now get:
No source
SOURCE: {}, OPTS: {:length=>2000}
SOURCE: {:length=>2000}, OPTS: {}
Source is mixed hash
SOURCE: {}, OPTS: {"River name"=>"Mississippi", :length=>2000, "Country"=>"USA"}
SOURCE: {"River name"=>"Mississippi", :length=>2000, "Country"=>"USA"}, OPTS: {}
In Ruby 3, you will get:
No source
SOURCE: {}, OPTS: {:length=>2000}
# ArgumentError, because only keywords provided and no positional arguments, and at least one positional argument is required
Source is mixed hash
SOURCE: {}, OPTS: {"River name"=>"Mississippi", :length=>2000, "Country"=>"USA"}
# ArgumentError, because only keywords provided and no positional arguments, and at least one positional argument is required
This shows there is still work do to in 2.7 to warn cases where the behavior will change in Ruby 3. In both cases, the calls to test2
should emit a warning, because the method will raise an error in Ruby 3.
Updated by mame (Yusuke Endoh) over 5 years ago
@jeremyevans0 (Jeremy Evans) Completely agreed.
Matz says that it is a bug to split keywords depending upon whether the key is Symbol or non-Symbol. (The behavior was introduced without matz's confirmation.) So I think it is okay that the behavior of Ruby 2.7 slightly changes.
Updated by jeremyevans0 (Jeremy Evans) over 5 years ago
- Status changed from Open to Closed
With https://github.com/ruby/ruby/commit/3463e83192215c36bdcebad8be907eaa09593a41, you now get warnings for calls where the behavior will change in Ruby 3:
No source
SOURCE: {}, OPTS: {:length=>2000}
file.rb:12: warning: The keyword argument for `test2' (defined at file.rb:5) is passed as the last hash parameter
SOURCE: {:length=>2000}, OPTS: {}
Source is mixed hash
SOURCE: {}, OPTS: {"River name"=>"Mississippi", :length=>2000, "Country"=>"USA"}
file.rb:19: warning: The keyword argument for `test2' (defined at file.rb:5) is passed as the last hash parameter
SOURCE: {"River name"=>"Mississippi", :length=>2000, "Country"=>"USA"}, OPTS: {}