Project

General

Profile

Actions

Bug #14130

closed

Keyword arguments are ripped from the middle of hash if argument have default value

Added by zverok (Victor Shepelev) almost 7 years ago. Updated about 5 years ago.

Status:
Closed
Target version:
-
[ruby-core:83881]

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.


Related issues 1 (0 open1 closed)

Related to Ruby master - Feature #14183: "Real" keyword argumentClosedActions
Actions #1

Updated by hsbt (Hiroshi SHIBATA) almost 7 years ago

Updated by marcandre (Marc-Andre Lafortune) about 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) about 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) about 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) about 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: {}
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0