Bug #20251
closedWrong ArgumentError raised for hash as last parameter before keyword aguments
Description
Suppose we have the following method as part of an API client:
def post(path, params, timeout: 30)
end
The method is then called like this:
post("/persons", name: "John Doe")
This leads to an error message ArgumentError: wrong number of arguments (given 1, expected 2)
. I suppose the (implicit) hash is used for keyword arguments.
My expectation would be, that:
- the Hash (passed without the brackets, but that is not needed in other cases) would be accepted as the
params
argument or - that an
ArgumentError
would be raised with the messageArgumentError: unknown keyword: :name
In my opinion, the first case should be what's happening, the second behaviour would be my expectation if that's not possible, because a hash without brackets can't be mixed without passing keyword arguments.
Either way, starting with Ruby 3.0, the behaviour makes no sense to me anymore.
Updated by nobu (Nobuyoshi Nakada) over 1 year ago
- Status changed from Open to Feedback
You need {
/}
to make the argument a Hash
instead of a keyword argument.
post("/persons", {name: "John Doe"})
The semantics around keyword arguments has been changed at 3.0.
Ruby 2.7 or earlier worked like as you described.
Updated by phillipp (Phillipp Röll) over 1 year ago
I know and unserstand that, but still, is the raised exception not wrong? Should that not be ArgumentError: unknown keyword: :name
?
Updated by jeremyevans0 (Jeremy Evans) over 1 year ago
phillipp (Phillipp Röll) wrote in #note-3:
I know and unserstand that, but still, is the raised exception not wrong? Should that not be
ArgumentError: unknown keyword: :name
?
Positional argument arity is checked before keyword arguments are checked. I don't think it makes sense to delay raising the error to try to find all additional possible errors. Certainly just showing unknown keyword: :name
is wrong, because even if you removed the keyword argument, the call would still have a positional argument arity error.
I think your main conceptual problem is here:
the Hash (passed without the brackets, but that is not needed in other cases) would be accepted as the params argument or
This is inaccurate starting in Ruby 3. Keyword arguments are separated from positional arguments. For backwards compatibility, a method that does not accept keyword arguments will convert the keyword arguments to a hash. However, the call itself does not pass a hash, it passes keywords. See https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/ for details.