Issue #16519 has been updated by jeremyevans0 (Jeremy Evans).


Dan0042 (Daniel DeLorme) wrote in #note-11:
> But here we're talking about the keyword-to-positional compatibility behavior, the one that you championed in #14183. Since this is for the sake of compatibility, it doesn't make sense to introduce incompatibility here. It's also very inconsistent to mix "strict" behavior for empty hashes and compatibility behavior for non-empty. Consider these bugs:
> 
> ```ruby
> def foo(opts); opts; end
> h = {}
> h[:k] = 1 if condition
> p foo(**h)  #=> {:k=>1} if condition is true
>             #=> warning (and eventually error) if condition is false
> 
> def bar(*args)
>   opts = args.pop if args.last.is_a?(Hash)
>   args
> end
> data = {"k"=>42}
> options = {d:5}
> bar(data, **options) #=> [] if options is empty; data was taken as opts!
> bar(data, **options) #=> [{"k"=>42}] if options is non-empty
> ```

The compatibility for treating keywords as a last positional hash is designed to allow code such as:

```ruby
def foo(opts={}) opts end
foo()
foo(opts)
foo(a: 1)
```

to continue to work. This type of code is all over the place in Ruby.  It's rare you would have a method take a mandatory options hash for keywords ("mandatory options"?), and rarer still that you would pass keywords to it using a double splat.  

Having a double splatted empty hash be removed is necessary for delegation to work correctly, and it makes more sense to keep the behavior consistent regardless of how the hash is created.  Additionally, the behavior is more consistent with explicit keywords:

```ruby
def foo(opts) opts end
foo()         # error
foo(**{})     # error
foo(a: 1)     # {a: 1}
foo(**{a: 1}) # {a: 1}
```

In my mind, if you have a mandatory argument, and you don't pass an argument to it, it should be an ArgumentError.  This is the same for regular splats:

```ruby
def foo(opts) opts end
foo(*[])       # error
foo(**{})      # error
foo(*[{a: 1}]) # {a: 1}
foo(**{a: 1})  # {a: 1}
```

Regarding your example with `bar`, the method will treat a positional hash argument as an options hash anyway, so it's basically broken in that regard.  Such methods are best to convert to keyword arguments if you want to be able to differentiate positional arguments from keywords/options.  Such code was probably written before keyword arguments, and it is unlikely that a double splat would be used when calling such methods.

----------------------------------------
Bug #16519: pp [Hash.ruby2_keywords_hash({})] shows `[nil]`
https://bugs.ruby-lang.org/issues/16519#change-84125

* Author: Eregon (Benoit Daloze)
* Status: Closed
* Priority: Normal
* ruby -v: ruby 2.8.0dev (2020-01-21T13:45:10Z master 5798d35ff6) [x86_64-linux]
* Backport: 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: REQUIRED
----------------------------------------
This happens on `master`:

```
$ ruby -ve 'ruby2_keywords def flag(*a); a.last; end; pp [flag(**{})]'
ruby 2.8.0dev (2020-01-21T13:45:10Z master 5798d35ff6) [x86_64-linux]
[nil]
```

Of course it should be `[{}]`, as it is for `pp [{}]`.

On 2.7.0 it warns (should be fixed, it's valid to `pp` a flagged Hash):
```
$ ruby -ve 'ruby2_keywords def flag(*a); a.last; end; pp [flag(**{})]'
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux]
[/home/eregon/.rubies/ruby-2.7.0/lib/ruby/2.7.0/pp.rb:226: warning: Passing the keyword argument as the last hash parameter is deprecated
/home/eregon/.rubies/ruby-2.7.0/lib/ruby/2.7.0/pp.rb:334: warning: The called method is defined here
{}]
```

The warning being in the middle of the output is a fun fact here.
Lines it refers to (still the same on current master):
https://github.com/ruby/ruby/blob/v2_7_0/lib/pp.rb#L226
https://github.com/ruby/ruby/blob/v2_7_0/lib/pp.rb#L334

This is very confusing as it can happen during `test-all` and then show output such as:
```
<[{:a=>1}]> expected but was
<[{:a=>1}, nil]>.
```
when the reality is (can be verified with `p` before the `assert_equal`):
```
<[{:a=>1}]> expected but was
<[{:a=>1}, {}]>.
```



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