Project

General

Profile

Actions

Bug #10856

closed

Updated by shugo (Shugo Maeda) over 10 years ago

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

Updated by shugo (Shugo Maeda) over 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 10 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) about 10 years ago

  • Has duplicate : Unexpected splatting of empty kwargs added

Updated by nobu (Nobuyoshi Nakada) about 10 years ago

  • Status changed from Closed to Open

Updated by matz (Yukihiro Matsumoto) about 10 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) almost 10 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 Diser
  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 = Diser.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) almost 10 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 9 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 9 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) almost 8 years ago

  • Has duplicate : Calling lambda with keyword arguments inconsistent behavior added

Updated by nobu (Nobuyoshi Nakada) almost 8 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 ]

  • vm_args.c (setup_parameters_complex): try keyword hash splat if
    given.

Updated by nobu (Nobuyoshi Nakada) almost 8 years ago

  • Related to : `belongs_to': unknown keywords: required, anonymous_class (ArgumentError) since Revision 59519 added

Updated by nobu (Nobuyoshi Nakada) almost 8 years ago

  • Related to : Compatible issue with keyword args behavior added

Updated by marcandre (Marc-Andre Lafortune) over 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 this before 2.5.0

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

Updated by hsbt (Hiroshi SHIBATA) over 7 years ago

  • Related to : "Real" keyword argument added

Updated by marcandre (Marc-Andre Lafortune) almost 7 years ago

  • Related to : Hash splat of empty hash should not create a positional argument. added

Updated by mame (Yusuke Endoh) over 6 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 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 ().

Updated by mame (Yusuke Endoh) over 6 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 6 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 ]

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0