Issue #11967 has been updated by Marc-Andre Lafortune. 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 ---------------------------------------- Bug #11967: Mixing kwargs with optional parameters changes way method parameters are parsed https://bugs.ruby-lang.org/issues/11967#change-56035 * Author: Markus Doits * Status: Rejected * Priority: Normal * Assignee: Yukihiro Matsumoto * ruby -v: ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin15] * Backport: 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN ---------------------------------------- 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? -- https://bugs.ruby-lang.org/ Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe> <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>