Bug #10856
closedSplat with empty keyword args gives unexpected results
Added by seantheprogrammer (Sean Griffin) over 9 years ago. Updated over 5 years ago.
Description
When keyword args are passed to a method with splat, and there are no keyword args, an empty hash is sent. I would expect no argument to be given, same as splat with an empty array. For example:
def foo
end
foo(**{})
This causes an argument error, as an empty hash is passed. I would expect the same behavior as
def foo
end
foo(*[])
Updated by shugo (Shugo Maeda) over 9 years ago
- Related to Bug #10719: empty splatting literal hash after other keywords causes SEGV added
Updated by shugo (Shugo Maeda) over 9 years ago
- Status changed from Open to Closed
Sean Griffin wrote:
When keyword args are passed to a method with splat, and there are no keyword args, an empty hash is sent. I would expect no argument to be given, same as splat with an empty array. For example:
It was fixed in r49193.
Updated by seantheprogrammer (Sean Griffin) over 9 years ago
It looks like this bug still exists in 2.2.1, and was only fixed when splatting a hash literal. The following code is still broken:
def foo
end
h = {}
foo(**h)
Updated by nobu (Nobuyoshi Nakada) over 9 years ago
- Has duplicate Misc #11131: Unexpected splatting of empty kwargs added
Updated by nobu (Nobuyoshi Nakada) over 9 years ago
- Status changed from Closed to Open
Updated by matz (Yukihiro Matsumoto) over 9 years ago
It's because ** tries to pass keyword hash (this caes empty) as an argument, so that old style
def foo(h)
end
foo(**{})
to work. In anyway, passing keyword arguments to a method that does not take any keyword argument can cause exception.
If you have real-world use-case, let us know.
Matz.
Updated by teamon (Tymon Tobolski) about 9 years ago
Hi Matz,
I think I just found a real-world use-case for exactly this issue - please take a look at the (simplified) example below.
class Dispatcher
def call(event, args)
public_send(event, **args)
end
def first_event(myarg:)
# ...
end
def second_event
# ...
end
end
And the call site looks like this:
disp = Dispatcher.new
disp.call(params[:event], params[:args])
Then we can observe:
disp.call(:first_event, {myarg: 123}) # => passes correctly, all good
disp.call(:first_event, {}) # => missing keyword: myarg - exactly what I'd expect
disp.call(:first_event, {myarg: 123, other: "foo"}) # => unknown keyword: other - exactly what I'd expect
disp.call(:second_event, {}) # => wrong number of arguments (1 for 0) - this /should/ just pass without error
So, in case the params[:args]
is empty we would expect to either get a "missing keyword" exception or simply valid method execution when such param is not required.
Please let me know what do you think about it.
Updated by marcandre (Marc-Andre Lafortune) about 9 years ago
I feel this has to be fixed.
foo(**{})
should === foo(**Hash.new)
in all cases, and I feel it should not raise an error.
Updated by gisborne (Guyren Howe) almost 8 years ago
I believe this behavior is wrong and should be fixed.
This gets in the way of simple functional programming idioms. eg "Call each of these functions with these args until one doesn't fail"
class FnSeries
def initialize(*fns)
@fns = fns
end
def call(*args, **kwargs)
@fns.each do |fn|
begin
return fn.call(*args, **kwargs)
rescue Exception => e
end
end
end
If one of the fns takes no args, this will fail even if that function would otherwise succeed.
Updated by Eregon (Benoit Daloze) almost 8 years ago
Guyren Howe wrote:
I believe this behavior is wrong and should be fixed.
This gets in the way of simple functional programming idioms. eg "Call each of these functions with these args until one doesn't fail"
There is a simple fix for your use-case, if you just want to fowrard arguments, don't use ** at all:
(it's not like in Python, keyword arguments are less separated form normal arguments)
class FnSeries def initialize(*fns) @fns = fns end def call(*args) @fns.each do |fn| begin return fn.call(*args) rescue Exception => e end end end
Marc-Andre Lafortune wrote:
I feel this has to be fixed.
foo(**{})
should ===foo(**Hash.new)
in all cases, and I feel it should not raise an error.
I agree, it's highly inconsistent that:
def foo(*args); args; end
foo(**{}) # => []
h={}
foo(**h) # => [{}]
foo(h) # => [{}]
Updated by nobu (Nobuyoshi Nakada) about 7 years ago
- Has duplicate Bug #13717: Calling lambda with keyword arguments inconsistent behavior added
Updated by nobu (Nobuyoshi Nakada) about 7 years ago
- Description updated (diff)
Updated by nobu (Nobuyoshi Nakada) about 7 years ago
- Status changed from Open to Closed
Applied in changeset trunk|r59519.
splat keyword hash
-
compile.c (compile_array_keyword_arg): set keyword splat flag if
explicitly splatted. [ruby-core:68124] [Bug #10856] -
vm_args.c (setup_parameters_complex): try keyword hash splat if
given.
Updated by nobu (Nobuyoshi Nakada) about 7 years ago
- Related to Bug #13791: `belongs_to': unknown keywords: required, anonymous_class (ArgumentError) since Revision 59519 added
Updated by nobu (Nobuyoshi Nakada) about 7 years ago
- Related to Bug #13793: Compatible issue with keyword args behavior added
Updated by marcandre (Marc-Andre Lafortune) almost 7 years ago
- Status changed from Closed to Open
- Assignee set to nobu (Nobuyoshi Nakada)
- Target version set to 2.5
This is not actually fixed.
def foo
puts "OK"
end
options = {}
foo(**options) # => OK (In 2.5.0preview1)
args = []
foo(*args, **options) # => ArgumentError: wrong number of arguments (given 1, expected 0)
The second call should also output "Ok".
Hopefully Nobu can crack this before 2.5.0
Updated by nobu (Nobuyoshi Nakada) almost 7 years ago
- Status changed from Open to Closed
Applied in changeset trunk|r60613.
compile.c: kw splat after splat
- compile.c (setup_args): set keyword splat flag after splat
arguments. [ruby-core:83638] [Bug #10856]
Updated by hsbt (Hiroshi SHIBATA) over 6 years ago
- Related to Feature #14183: "Real" keyword argument added
Updated by marcandre (Marc-Andre Lafortune) almost 6 years ago
- Related to Bug #15078: Hash splat of empty hash should not create a positional argument. added
Updated by mame (Yusuke Endoh) over 5 years ago
- Status changed from Closed to Assigned
- Target version changed from 2.5 to 2.7
marcandre (Marc-Andre Lafortune) wrote:
This is not actually fixed.
def foo puts "OK" end options = {} foo(**options) # => OK (In 2.5.0preview1) args = [] foo(*args, **options) # => ArgumentError: wrong number of arguments (given 1, expected 0)
The second call should also output "Ok".
Hopefully Nobu can crack this before 2.5.0
This is not completely fixed yet:
$ ruby -v
ruby 2.6.0p0 (2018-12-25 revision 66547) [x86_64-linux]
$ ruby -e 'def foo; end; options = {}; args = []; foo(*args, **options)'
$ ruby -e 'def foo(z); end; options = {}; args = []; foo(*args, 1, **options)'
Traceback (most recent call last):
1: from -e:1:in `<main>'
-e:1:in `foo': wrong number of arguments (given 2, expected 1) (ArgumentError)
I go for the exception. opt = {}; foo(**option)
should consistently pass an empty hash instead of ignoring it. It is not intuitive, but it IS the current spec of keyword argument. This is a design flaw in the current spec. I believe that it must be fixed by complete separation between keyword arguments and positional arguments (#14183).
Updated by mame (Yusuke Endoh) over 5 years ago
Another presentation of the bug:
def foo; end
foo(*[], {}) #=> does not raise an exception in 2.6
Updated by mame (Yusuke Endoh) over 5 years ago
- Status changed from Assigned to Closed
Applied in changeset trunk|r67256.
compile.c: fix the corner case of rest and keyword arguments
See https://bugs.ruby-lang.org/issues/10856#note-20 . [Bug #10856]