diff options
| author | Jeremy Evans <code@jeremyevans.net> | 2023-12-07 08:35:55 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-07 08:35:55 -0800 |
| commit | ca204a20231f1ecabf5a7343aec49c3ea1bac90a (patch) | |
| tree | 95ceaa2753cfe0763b57c4fdf420bfa820f01118 /test/ruby | |
| parent | e6a6ea9dcf0741f415ccbdcdf7643315f032e4f9 (diff) | |
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 <nobu@ruby-lang.org>
Diffstat (limited to 'test/ruby')
| -rw-r--r-- | test/ruby/test_keyword.rb | 26 |
1 files changed, 20 insertions, 6 deletions
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index ef594bd52e..9aca787dff 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -237,16 +237,16 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(true, Hash.ruby2_keywords_hash?(marked)) end + def assert_equal_not_same(kw, res) + assert_instance_of(Hash, res) + assert_equal(kw, res) + assert_not_same(kw, res) + end + def test_keyword_splat_new kw = {} h = {a: 1} - def self.assert_equal_not_same(kw, res) - assert_instance_of(Hash, res) - assert_equal(kw, res) - assert_not_same(kw, res) - end - def self.yo(**kw) kw end m = method(:yo) assert_equal(false, yo(**{}).frozen?) @@ -459,6 +459,20 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal_not_same h, yo(*a, **h) end + def test_keyword_splat_to_non_keyword_method + h = {a: 1}.freeze + + def self.yo(kw) kw end + assert_equal_not_same(h, yo(**h)) + assert_equal_not_same(h, method(:yo).(**h)) + assert_equal_not_same(h, :yo.to_proc.(self, **h)) + + def self.yoa(*kw) kw[0] end + assert_equal_not_same(h, yoa(**h)) + assert_equal_not_same(h, method(:yoa).(**h)) + assert_equal_not_same(h, :yoa.to_proc.(self, **h)) + end + def test_regular_kwsplat kw = {} h = {:a=>1} |
