From: justcolin@... Date: 2016-02-13T17:13:25+00:00 Subject: [ruby-core:73794] [Ruby trunk Bug#12022] Inconsistent behavior with splatted named arguments Issue #12022 has been updated by Colin Fulton. Additionally I just noticed that the following related bug also happens with the double splat: ```ruby def example(required_param, **optional_named) p required_param end # Works as expected. example(42) # => 42 # Should raise an error because no positional argument is being passed in, # instead it passes in an option hash to required_param. example(named_arg: 43) def example2(required_param, second_required_param, **optional_named) # Some code... end # Should say it is missing two arguments, instead says it is missing one: example2(named: 42) ``` I understand that all of this is probably related to the option hash syntax leftover from when Ruby didn't have named arguments, but this all leads to very confusing code. Named arguments are much more powerful, easier to deal with, and more "Ruby-like" than the option hash syntax. As such I feel that Ruby should assume that if you use the ** operator in your parameters you never want that method to use an options hash. In the distance future I even think that option hashes should be deprecated then removed, since named arguments do everything option hashes do and more. What opinions do you all have? ---------------------------------------- Bug #12022: Inconsistent behavior with splatted named arguments https://bugs.ruby-lang.org/issues/12022#change-56975 * Author: Colin Fulton * Status: Open * Priority: Normal * Assignee: * 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: