Bug #17178
closedProcs with kw:def/**kw lose elements when called with a single Array
Description
$ ruby -ve '{a: 1}.each_pair { |a, kw: :def| p [a,kw] }'
ruby 3.0.0dev (2020-09-18T09:12:58Z master e1cca08a6d) [x86_64-linux]
[:a, :def] # bug, should be [[1, 2], :def]
$ ruby -ve '{a: 1}.each_pair { |a, **kw| p [a,kw] }'
ruby 3.0.0dev (2020-09-18T09:12:58Z master e1cca08a6d) [x86_64-linux]
[:a, {}] # bug, should be [[1, 2], {}]
So the value of the key-value pair is lost and not assigned to any argument.
This seems a general problem of Proc, not just #each_pair:
$ ruby -e 'proc { |a, kw: :def| p [a,kw] }.call(1,2)'
[1, :def] # understandable
$ ruby -e 'proc { |a, kw: :def| p [a,kw] }.call([1,2])'
[1, :def] # bug, should be [[1, 2], :def]
$ ruby -e 'proc { |a, **kw| p [a,kw] }.call(1,2)'
[1, {}] # understandable
$ ruby -e 'proc { |a, **kw| p [a,kw] }.call([1,2])'
[1, {}] # bug, should be [[1, 2], {}]
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
Eregon (Benoit Daloze) wrote:
$ ruby -ve '{a: 1}.each_pair { |a, kw: :def| p [a,kw] }' ruby 3.0.0dev (2020-09-18T09:12:58Z master e1cca08a6d) [x86_64-linux] [:a, :def] # bug, should be [[1, 2], :def] $ ruby -ve '{a: 1}.each_pair { |a, **kw| p [a,kw] }' ruby 3.0.0dev (2020-09-18T09:12:58Z master e1cca08a6d) [x86_64-linux] [:a, {}] # bug, should be [[1, 2], {}]
So the value of the key-value pair is lost and not assigned to any argument.
This seems a general problem of Proc, not just #each_pair:
$ ruby -e 'proc { |a, kw: :def| p [a,kw] }.call(1,2)' [1, :def] # understandable $ ruby -e 'proc { |a, kw: :def| p [a,kw] }.call([1,2])' [1, :def] # bug, should be [[1, 2], :def] $ ruby -e 'proc { |a, **kw| p [a,kw] }.call(1,2)' [1, {}] # understandable $ ruby -e 'proc { |a, **kw| p [a,kw] }.call([1,2])' [1, {}] # bug, should be [[1, 2], {}]
Are we sure this is a bug, and not just the result of a different behavior of proc{|a|}
proc{|a,|}
, with keywords being treated like the latter (autosplatting) as a comma is used?
Keywords in procs have always caused autosplatting of the proc. With Ruby 3, we could change this if there were only a single argument and keywords, but it seems more like a new feature than a bug fix.
Updated by Eregon (Benoit Daloze) over 4 years ago
Is there a reason that keywords in procs cause autosplatting?
It seems counter-intuitive.
I would expect autosplatting depends on declared positional parameters only.
I'd argue it's a bug, because we lose part of the arguments for seemingly no reason.
Also keyword extension is supposed to be safe, right? Here it loses part of the arguments, which seems rather severe to me:
proc { |a| p [a] }.call([1,2,3]) # => [[1, 2, 3]]
proc { |a, **kw| p [a,kw] }.call([1,2,3]) # => [1, {}]
(where are 2
and 3
??)
proc { |a,| p [a] }.call([1,2,3]) # => [1]
this is OK, I'm explicitly discarding extra arguments.
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
Eregon (Benoit Daloze) wrote in #note-2:
Is there a reason that keywords in procs cause autosplatting?
It seems counter-intuitive.
I would expect autosplatting depends on declared positional parameters only.
Previously, the autosplatted argument could be used as keywords if they were a hash or implicitly convertible to a hash, though this is no longer true in Ruby 3.
I'd argue it's a bug, because we lose part of the arguments for seemingly no reason.
Also keyword extension is supposed to be safe, right? Here it loses part of the arguments, which seems rather severe to me:
proc { |a| p [a] }.call([1,2,3]) # => [[1, 2, 3]]
proc { |a, **kw| p [a,kw] }.call([1,2,3]) # => [1, {}]
(where are2
and3
??)
proc { |a,| p [a] }.call([1,2,3]) # => [1]
this is OK, I'm explicitly discarding extra arguments.
I agree that your suggested behavior makes more intuitive sense with Ruby 3 semantics, but not with Ruby 2 semantics. I'm not sure the loss of backwards compatibility is worth it, and this will break backwards compatibility. This is something @matz (Yukihiro Matsumoto) should decide.
Updated by Eregon (Benoit Daloze) over 4 years ago
- Assignee set to matz (Yukihiro Matsumoto)
jeremyevans0 (Jeremy Evans) wrote in #note-3:
I'm not sure the loss of backwards compatibility is worth it, and this will break backwards compatibility.
It would only break code that explicitly loses arguments that way, which I would think is never intended.
It's probably also quite rare to use any keyword arguments for blocks.
The fact we (AFAIK) did not have many bug reports about this before sounds to me like very few people ever run into this weird behavior.
Assigned to matz.
Updated by matz (Yukihiro Matsumoto) about 4 years ago
- Status changed from Open to Closed
I think it should be as it is. It would break compatibility from Ruby2.x. It's not that big issue worh breaking.
Matz.