Project

General

Profile

Actions

Bug #10856

closed

Splat with empty keyword args gives unexpected results

Added by seantheprogrammer (Sean Griffin) almost 10 years ago. Updated over 5 years ago.

Status:
Closed
Target version:
ruby -v:
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin13]
[ruby-core:68124]

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(*[])

Related issues 7 (0 open7 closed)

Related to Ruby master - Bug #10719: empty splatting literal hash after other keywords causes SEGVClosednobu (Nobuyoshi Nakada)01/09/2015Actions
Related to Ruby master - Bug #13791: `belongs_to': unknown keywords: required, anonymous_class (ArgumentError) since Revision 59519Closednobu (Nobuyoshi Nakada)Actions
Related to Ruby master - Bug #13793: Compatible issue with keyword args behaviorClosedActions
Related to Ruby master - Feature #14183: "Real" keyword argumentClosedActions
Related to Ruby master - Bug #15078: Hash splat of empty hash should not create a positional argument.Closedmatz (Yukihiro Matsumoto)Actions
Has duplicate Ruby master - Misc #11131: Unexpected splatting of empty kwargsClosedActions
Has duplicate Ruby master - Bug #13717: Calling lambda with keyword arguments inconsistent behaviorClosedActions
Actions #1

Updated by shugo (Shugo Maeda) almost 10 years ago

  • Related to Bug #10719: empty splatting literal hash after other keywords causes SEGV added

Updated by shugo (Shugo Maeda) almost 10 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)
Actions #4

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) over 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) over 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) over 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) over 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) # => [{}]
Actions #11

Updated by nobu (Nobuyoshi Nakada) over 7 years ago

  • Has duplicate Bug #13717: Calling lambda with keyword arguments inconsistent behavior added
Actions #13

Updated by nobu (Nobuyoshi Nakada) over 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.

Actions #14

Updated by nobu (Nobuyoshi Nakada) over 7 years ago

  • Related to Bug #13791: `belongs_to': unknown keywords: required, anonymous_class (ArgumentError) since Revision 59519 added
Actions #15

Updated by nobu (Nobuyoshi Nakada) over 7 years ago

  • Related to Bug #13793: Compatible issue with keyword args behavior added

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

Actions #17

Updated by nobu (Nobuyoshi Nakada) about 7 years ago

  • Status changed from Open to Closed

Applied in changeset trunk|r60613.


compile.c: kw splat after splat

Actions #18

Updated by hsbt (Hiroshi SHIBATA) almost 7 years ago

Actions #19

Updated by marcandre (Marc-Andre Lafortune) about 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
Actions #22

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]

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0