[ruby-core:94711] [Ruby master Bug#12022] Inconsistent behavior with splatted named arguments

From: merch-redmine@...
Date: 2019-09-02 04:12:07 UTC
List: ruby-core #94711
Issue #12022 has been updated by jeremyevans0 (Jeremy Evans).

Status changed from Open to Closed

With the master branch, you now get:

```ruby
without_parameters(*array)
# => nil

without_parameters(**empty_hash)
# => nil

with_parameters(**empty_hash)
# => []

# This doesn't raise ArgumentError, because the method does not accept keyword
# arguments, so a keyword splat is passed as a positional hash.
with_parameters(**filled_hash)
# => [{:example=>"value"}]

with_one_parameter(**empty_hash)
# ArgumentError (wrong number of arguments (given 0, expected 1))

# This also doesn't raise ArgumentError, for the same reason as with_parameters
with_one_parameter(**filled_hash)
# => {:example=>"value"}
```

There are no warnings in this code, and the behavior should be the same in Ruby 3.

----------------------------------------
Bug #12022: Inconsistent behavior with splatted named arguments
https://bugs.ruby-lang.org/issues/12022#change-81321

* Author: justcolin (Colin Fulton)
* Status: Closed
* Priority: Normal
* Assignee: 
* Target version: 
* ruby -v: 2.2.2 and 2.3.0
* Backport: 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN
----------------------------------------
# The Bug

When an empty hash is splatted (using \*\*) into a call of a method with no parameters, the empty hash is passed in as a positional argument *instead* of doing nothing. This causes an ArgumentError, which is confusing because when you splat an empty array into a method that doesn't accept any arguments the method is called without raising an error.

Similarly, if you splat a hash into a method that only has positional arguments, the method is called with the hash added as the last argument. This either causes an ArgumentError or unexpected bugs.

## Examples
(tested in MRI 2.2.2 and 2.3.0)

```ruby
def without_parameters
  # some code
end

def with_parameters(*args)
  args
end

def with_one_parameter(arg)
  arg
end

empty_hash  = {}
filled_hash = { example: "value" }
array       = []

without_parameters(*array)
# calls the method without an error because `array' is empty

without_parameters(**empty_hash)
# unexpectedly raises an ArgumentError despite `empty_hash' being empty

with_parameters(**empty_hash)
# unexpectedly returns [{}] instead of []

with_parameters(**filled_hash)
# unexpectedly returns [{ example: "value }] instead of raising an ArgumentError

with_one_parameter(**empty_hash)
with_one_parameter(**filled_hash)
# both unexpectedly do not raise an ArgumentError
```

# Further Information

This behavior makes it more difficult to do things like write specialized decorator classes using #method_missing. The following example does not work if the method being called does not have any named parameters. The variable named_args gets passed in as a positional argument, causing ArgumentErrors or unexpected bugs:

```ruby
  class TrivialDecoratorExample
    def initialize(value)
      @value = value
    end

    def method_missing(name, *args, **named_args, &block)
      @value.send(name, *args, **named_args, &block)
    end
  end
```

Instead one has to write something really ugly like:

```ruby
def method_missing(name, *args, **named_args, &block)
  if @value.method(name)
           .parameters
           .any? { |type, _| [:keyreq, :key].include?(type) }

    @value.send(name, *args, **named_args, &block)
  elsif named_args.empty?
    @value.send(name, *args, &block)
  else
    raise ArgumentError.new
  end
end
```



-- 
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>

In This Thread

Prev Next