From ca204a20231f1ecabf5a7343aec49c3ea1bac90a Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 7 Dec 2023 08:35:55 -0800 Subject: Fix keyword splat passing as regular argument Since Ruby 3.0, Ruby has passed a keyword splat as a regular argument in the case of a call to a Ruby method where the method does not accept keyword arguments, if the method call does not contain an argument splat: ```ruby def self.f(obj) obj end def self.fs(*obj) obj[0] end h = {a: 1} f(**h).equal?(h) # Before: true; After: false fs(**h).equal?(h) # Before: true; After: false a = [] f(*a, **h).equal?(h) # Before and After: false fs(*a, **h).equal?(h) # Before and After: false ``` The fact that the behavior differs when passing an empty argument splat makes it obvious that something is not working the way it is intended. Ruby 2 always copied the keyword splat hash, and that is the expected behavior in Ruby 3. This bug is because of a missed check in setup_parameters_complex. If the keyword splat passed is not mutable, then it points to an existing object and not a new object, and therefore it must be copied. Now, there are 3 specs for the broken behavior of directly using the keyword splatted hash. Fix two specs and add a new version guard. Do not keep the specs for the broken behavior for earlier Ruby versions, in case this fix is backported. For the ruby2_keywords spec, just remove the related line, since that line is unrelated to what the spec is testing. Co-authored-by: Nobuyoshi Nakada --- spec/ruby/language/hash_spec.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'spec/ruby/language/hash_spec.rb') diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb index 9e2b9bd4c5..6ac382c42c 100644 --- a/spec/ruby/language/hash_spec.rb +++ b/spec/ruby/language/hash_spec.rb @@ -220,15 +220,17 @@ describe "The ** operator" do h.should == { one: 1, two: 2 } end - it "does not copy when calling a method taking a positional Hash" do - def m(h) - h.delete(:one); h - end + ruby_bug "#20012", ""..."3.3" do + it "makes a copy when calling a method taking a positional Hash" do + def m(h) + h.delete(:one); h + end - h = { one: 1, two: 2 } - m(**h).should == { two: 2 } - m(**h).should.equal?(h) - h.should == { two: 2 } + h = { one: 1, two: 2 } + m(**h).should == { two: 2 } + m(**h).should_not.equal?(h) + h.should == { one: 1, two: 2 } + end end ruby_version_is "3.1" do -- cgit v1.2.3