From: merch-redmine@... Date: 2019-08-31T07:26:50+00:00 Subject: [ruby-core:94699] [Ruby master Bug#14415] Empty keyword hashes get assigned to ordinal args. Issue #14415 has been updated by jeremyevans0 (Jeremy Evans). With recent changes to the master branch, you now the get the following results: ```ruby kws = {} ->{}.call **kws # => nil ->{}.call **{} # => nil ->{}.call **({}) # => nil ->{}.call **({};) # => nil ->{}.call **(;{}) # => nil ->{}.call **{**{}} # => nil ->{}.call **({};{}) # => nil ->{}.call **{**kws} # => nil ->a{a}.call **{} rescue $! # => # ->a{a}.call **kws rescue $! # => # ->a{a}.call **(;{}) rescue $! # => # ->a,b:{}.call **{b:1} rescue $! # warning: The keyword argument for `call' (defined at (irb):13) is passed as the last hash parameter # => # ->a,b:,**c{[a,b,c]}.call 1, b:2 # => [1, 2, {}] ->a,b:,**c{[a,b,c]}.call 1, b:2, 3=>4 # => [1, 2, {3=>4}] ->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 rescue $! # warning: The keyword argument for `call' (defined at (irb):16) is passed as the last hash parameter # => ArgumentError (missing keyword: :b) ->a,b:,**c{[a,b,c]}.call 1=>2, **{b:3} rescue $! # warning: The keyword argument for `call' (defined at (irb):17) is passed as the last hash parameter # => # ->a,b:,**c{[a,b,c]}.call({1=>2}, {b: 3}) # warning: The last argument for `call' (defined at (irb):19) is used as the keyword parameter # => [{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], 2, {:c=>3, 4=>5}] [*[1,2], *[:c, :d]] # => [1, 2, :c, :d] {**{1=>2}, **{c: :d}} # => {1=>2, :c=>:d} [1,2,**{a:3}] # => [1, 2, {:a=>3}] [1,2,**{}] # => [1, 2] [1,2,**kws] # => [1, 2, {}] -> a='a', b:'b' { [a, b] }.call a: 2, b: 3 rescue $! # => # -> a='a', b:'b' { [a, b] }.call 1 => 2, b: 3 # warning: The last argument for `call' (defined at (irb):2) is split into positional and keyword parameters # => [{1=>2}, 3] -> a='a', b:'b' { [a, b] }.call b: 3 # => ["a", 3] -> a { a }.call b: 3 # => {:b=>3} -> a, **b { [a, b] }.call b: 3 rescue $! # warning: The keyword argument for `call' (defined at (irb):5) is passed as the last hash parameter # => [{:b=>3}, {}] ``` For the calls that warn, you will get the following behavior in Ruby 3: ```ruby ->a,b:{}.call **{b:1} rescue $! # => ArgumentError (wrong number of arguments (given 0, expected 1)) ->a,b:,**c{[a,b,c]}.call 1=>2, b:3 rescue $! # => ArgumentError (wrong number of arguments (given 0, expected 1)) ->a,b:,**c{[a,b,c]}.call 1=>2, **{b:3} # => ArgumentError (wrong number of arguments (given 0, expected 1)) ->a,b:,**c{[a,b,c]}.call({1=>2}, {b: 3}) # => ArgumentError (wrong number of arguments (given 2, expected 1)) -> a='a', b:'b' { [a, b] }.call 1 => 2, b: 3 # ['a', {1 => 2, :b => 3}] -> a, **b { [a, b] }.call b: 3 rescue $! # => ArgumentError (wrong number of arguments (given 0, expected 1)) ``` 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 `[]`. ---------------------------------------- Bug #14415: Empty keyword hashes get assigned to ordinal args. https://bugs.ruby-lang.org/issues/14415#change-81308 * Author: josh.cheek (Josh Cheek) * Status: Open * 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 $! # => # ->{}.call **({}) # => nil ->{}.call **({};) # => nil ->{}.call **(;{}) rescue $! # => # ->{}.call **{**{}} # => nil ->{}.call **({};{}) rescue $! # => # ->{}.call **{**kws} rescue $! # => # ~~~ 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 $! # => # ->a{a}.call **kws # => {} ->a{a}.call **(;{}) # => {} (;{}) # => {} ~~~ Further confusion, it's missing `a`, not `b`: ~~~ruby ->a,b:{}.call **{b:1} rescue $! # => # ~~~ 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 $! # => # ->a,b:,**c{[a,b,c]}.call 1=>2, b:3 rescue $! # => # ->a,b:,**c{[a,b,c]}.call 1=>2, **{b:3} rescue $! # => # ->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 $! # => # [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: