Bug #11967
closedMixing kwargs with optional parameters changes way method parameters are parsed
Description
I have the following method:
def test(first_param = nil, keyword_arg: nil)
puts "first_param: #{first_param}"
puts "keyword_arg: #{keyword_arg}"
end
All the following calls do what I expect them to do:
test(:something)
#=> first_param: something
# keyword_arg:
test(nil, keyword_arg: :keyword_arg)
#=> first_param:
# keyword_arg: keyword_arg
test({ first_param: :is_a_hash }, keyword_arg: :is_still_working)
#=> first_param: {:first_param=>:is_a_hash}
# keyword_arg: is_still_working
But omitting the optional keyword_arg
and passing a hash as first argument gives me an error:
test(first_param: :is_a_hash)
#=> test.rb:1:in `test': unknown keyword: first_param (ArgumentError)
# from test.rb:12:in `<main>'
I'd expect it to set first_param
to { first_param: :is_hash }
and keyword_arg
being nil
.
It seems it is interpreting every hash as keyword arg:
test(keyword_arg: :should_be_first_param)
#=> first_param:
# keyword_arg: should_be_first_param
This should have set first_param
to { keyword_arg: :should_be_first_param }
, leaving keyword_arg
nil
in my opinion.
Making the first parameter mandatory and everything works like I'd expect to:
def test(first_param, keyword_arg: nil)
puts "first_param: #{first_param}"
puts "keyword_arg: #{keyword_arg}"
end
test(first_param: :is_a_hash)
#=> first_param: {:first_param=>:is_a_hash}
# keyword_arg:
test(keyword_arg: :should_be_first_param)
#=> first_param: {:keyword_arg=>:should_be_first_param}
#=> keyword_arg:
I'd expect making a parameter optional does not change the way parameters are parsed.
Is this a parser oddity or expected behaviour?
Updated by markus_d (Markus Doits) almost 9 years ago
- Subject changed from Mixing kwargs with optional parameters change way method parameters are parsed to Mixing kwargs with optional parameters changes way method parameters are parsed
Updated by duerst (Martin Dürst) almost 9 years ago
- Assignee set to matz (Yukihiro Matsumoto)
This looks like a spec issue, so I have assigned it to Matz. I can see arguments for both ways, the current one and the one proposed by Markus. But we can't have it both ways :-(.
Updated by marcandre (Marc-Andre Lafortune) almost 9 years ago
- Status changed from Open to Rejected
This behavior may be surprising but it is intentional.
It boils down to giving priority to filling keyword arguments first instead of filling unnamed parameters. It is actually the only possible way to go. Among other things, think about the following example:
def foo(*rest, bar: 42)
end
If we don't prioritize named arguments first, then there is simply no way to specify a value for bar
in this example!
So Ruby checks that:
- after all mandatory unnamed arguments are filled
- if the last remaining argument is hash-like
- and all its keys are symbols
- and the method called uses keyword arguments
=> then that parameter is used for keyword arguments.
Note the requirement on keys being symbols. This can yield even more surprising if you pass a hash with some keys that are not symbols:
def foo(a = nil, b: nil)
p a, b
end
foo(:b => 42) # => nil, 42
foo('b' => 42) # => {"b" => 42}, nil
Updated by hsbt (Hiroshi SHIBATA) about 7 years ago
- Related to Feature #14183: "Real" keyword argument added