[ruby-core:94747] [Ruby master Bug#14415] Empty keyword hashes get assigned to ordinal args.
From:
merch-redmine@...
Date:
2019-09-02 15:32:02 UTC
List:
ruby-core #94747
Issue #14415 has been updated by jeremyevans0 (Jeremy Evans).
Status changed from Open to Closed
jeremyevans0 (Jeremy Evans) wrote:
> I think the only case that is questionable still is:
>
> ```ruby
> [**({};)]
> # => []
> [**(;{})] # and h = {}; [**h]
> # => [{}]
> ```
>
> The keyword argument separation changes just made to the master branch did not affect this code, since it isn't a method call. This behavior has been present since Ruby 2.2. I think it would be a good idea to make both `[**({};)]` and `[**(;{})]` return `[]`.
Recent changes to the master branch have fixed this issue. Keyword splats of empty hashes in arrays no longer add an empty hash to the array:
```ruby
[**(;{})]
# => []
```
----------------------------------------
Bug #14415: Empty keyword hashes get assigned to ordinal args.
https://bugs.ruby-lang.org/issues/14415#change-81364
* Author: josh.cheek (Josh Cheek)
* Status: Closed
* Priority: Normal
* Assignee:
* Target version:
* ruby -v: ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin17]
* Backport: 2.3: UNKNOWN, 2.4: UNKNOWN, 2.5: UNKNOWN
----------------------------------------
Spreading empty arrays works, even when they go through a variable, or are disguised:
~~~ruby
args = [] # => []
->{}.call *[] # => nil
->{}.call *args # => nil
->{}.call *([]) # => nil
->{}.call *([];) # => nil
->{}.call *(;[]) # => nil
->{}.call *[*[]] # => nil
->{}.call *([];[]) # => nil
->{}.call *[*args] # => nil
~~~
Spreading empty keywords does not, when going through a variable, or sufficiently disguised:
~~~ruby
kws = {} # => {}
->{}.call **{} # => nil
->{}.call **kws rescue $! # => #<ArgumentError: wrong number of arguments (given 1, expected 0)>
->{}.call **({}) # => nil
->{}.call **({};) # => nil
->{}.call **(;{}) rescue $! # => #<ArgumentError: wrong number of arguments (given 1, expected 0)>
->{}.call **{**{}} # => nil
->{}.call **({};{}) rescue $! # => #<ArgumentError: wrong number of arguments (given 1, expected 0)>
->{}.call **{**kws} rescue $! # => #<ArgumentError: wrong number of arguments (given 1, expected 0)>
~~~
It seems that `**{}` gets optimized out of the code, as expected. Likely due to https://bugs.ruby-lang.org/issues/10719
But `**empty_kws` still gets incorrectly passed as a hash, despite an attempt to fix it in https://bugs.ruby-lang.org/issues/13717
~~~ruby
->a{a}.call **{} rescue $! # => #<ArgumentError: wrong number of arguments (given 0, expected 1)>
->a{a}.call **kws # => {}
->a{a}.call **(;{}) # => {}
(;{}) # => {}
~~~
Further confusion, it's missing `a`, not `b`:
~~~ruby
->a,b:{}.call **{b:1} rescue $! # => #<ArgumentError: missing keyword: b>
~~~
Treating keywords as a special form of hash makes them very difficult to reason about.
Arrays manage to pull off destructuring and spreading with no issue, as we saw above.
I just want hashes to work like arrays with named matching instead of ordinal matching.
For each example below, try looking at the LHS and predicting what the result will be.
~~~ruby
->a,b:,**c{[a,b,c]}.call 1, b:2 # => [1, 2, {}]
->a,b:,**c{[a,b,c]}.call 1, b:2, 3=>4 rescue $! # => #<ArgumentError: wrong number of arguments (given 2, expected 1; required keyword: b)>
->a,b:,**c{[a,b,c]}.call 1=>2, b:3 rescue $! # => #<ArgumentError: missing keyword: b>
->a,b:,**c{[a,b,c]}.call 1=>2, **{b:3} rescue $! # => #<ArgumentError: missing keyword: b>
->a,b:,**c{[a,b,c]}.call({1=>2}, b: 3) # => [{1=>2}, 3, {}]
->a,b:,**c{[a,b,c]}.call({1=>2}, {b: 3}) # => [{1=>2}, 3, {}]
->*a {a }.call 1, b:2, c:3, 4=>5 # => [1, {:b=>2, :c=>3, 4=>5}]
->*a,b:,**c{[a,b,c]}.call 1, b:2, c:3, 4=>5 # => [[1, {4=>5}], 2, {:c=>3}]
~~~
Keywords are getting in the way of beautiful hash spreading!
~~~ruby
[*[1,2], *[:c, :d]] # => [1, 2, :c, :d]
{**{1=>2}, **{c: :d}} rescue $! # => #<TypeError: wrong argument type Integer (expected Symbol)>
[1,2,**{a:3}] # => [1, 2, {:a=>3}]
[1,2,**{}] # => [1, 2]
[1,2,**kws] # => [1, 2, {}]
~~~
Note that the latest JS's behaviour is congruent with my expected outputs:
~~~sh
$ node -v
# >> v8.9.4
$ node -p '
(({a, c, ...rest}) => [a, c, rest])
({a: 1, b: 2, c: 3, d: 4})
'
# >> [ 1, 3, { b: 2, d: 4 } ]
$ node -p '
const a=1, b=2, e={f: 5, g: 6}
;({...{a, b}, ...{c: 3, d: 4}, ...e})
'
# >> { a: 1, b: 2, c: 3, d: 4, f: 5, g: 6 }
~~~
--
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>