diff options
author | Jeremy Evans <code@jeremyevans.net> | 2023-12-12 07:34:53 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-12 07:34:53 -0800 |
commit | 2f1d6da8c45590bf3461ed4bf051a4e1009eaf85 (patch) | |
tree | 8e868e9b6a71fe74c323dfa2e81422d08beadf6f /test/ruby/test_call.rb | |
parent | f671c5d1791d14a44902cfe672ae6b457d1592e9 (diff) |
Fix op asgn calls with keywords
Examples of such calls:
```ruby
obj[kw: 1] += fo
obj[**kw] &&= bar
```
Before this patch, literal keywords would segfault in the compiler,
and keyword splat usage would result in TypeError.
This handles all cases I can think of:
* literal keywords
* keyword splats
* combined with positional arguments
* combined with regular splats
* both with and without blocks
* both popped and non-popped cases
This also makes sure that to_hash is only called once on the keyword
splat argument, instead of twice, and make sure it is called before
calling to_proc on a passed block.
Fixes [Bug #20051]
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Diffstat (limited to 'test/ruby/test_call.rb')
-rw-r--r-- | test/ruby/test_call.rb | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index 92c5e604b5..f17d5848b9 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -121,6 +121,166 @@ class TestCall < Test::Unit::TestCase assert_equal([10], a(10)) end + def test_call_op_asgn_keywords + h = Class.new do + attr_reader :get, :set + def v; yield; [*@get, *@set] end + def [](*a, **b, &c) @get = [a, b, c]; @set = []; 3 end + def []=(*a, **b, &c) @set = [a, b, c] end + end.new + + a = [] + kw = {} + b = lambda{} + + # +=, without block, non-popped + assert_equal([[], {}, nil, [4], {}, nil], h.v{h[**kw] += 1}) + assert_equal([[0], {}, nil, [0, 4], {}, nil], h.v{h[0, **kw] += 1}) + assert_equal([[0], {}, nil, [0, 4], {}, nil], h.v{h[0, *a, **kw] += 1}) + assert_equal([[], {kw: 5}, nil, [4], {kw: 5}, nil], h.v{h[kw: 5] += 1}) + assert_equal([[], {kw: 5, a: 2}, nil, [4], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] += 1}) + assert_equal([[], {kw: 5, a: 2}, nil, [4], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] += 1}) + assert_equal([[0], {kw: 5, a: 2}, nil, [0, 4], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] += 1}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil, [0, 4], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] += 1}) + + # +=, with block, non-popped + assert_equal([[], {}, b, [4], {}, b], h.v{h[**kw, &b] += 1}) + assert_equal([[0], {}, b, [0, 4], {}, b], h.v{h[0, **kw, &b] += 1}) + assert_equal([[0], {}, b, [0, 4], {}, b], h.v{h[0, *a, **kw, &b] += 1}) + assert_equal([[], {kw: 5}, b, [4], {kw: 5}, b], h.v{h[kw: 5, &b] += 1}) + assert_equal([[], {kw: 5, a: 2}, b, [4], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] += 1}) + assert_equal([[], {kw: 5, a: 2}, b, [4], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] += 1}) + assert_equal([[0], {kw: 5, a: 2}, b, [0, 4], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] += 1}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b, [0, 4], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1}) + + # +=, without block, popped + assert_equal([[], {}, nil, [4], {}, nil], h.v{h[**kw] += 1; nil}) + assert_equal([[0], {}, nil, [0, 4], {}, nil], h.v{h[0, **kw] += 1; nil}) + assert_equal([[0], {}, nil, [0, 4], {}, nil], h.v{h[0, *a, **kw] += 1; nil}) + assert_equal([[], {kw: 5}, nil, [4], {kw: 5}, nil], h.v{h[kw: 5] += 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil, [4], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] += 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil, [4], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] += 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, nil, [0, 4], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] += 1; nil}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil, [0, 4], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] += 1; nil}) + + # +=, with block, popped + assert_equal([[], {}, b, [4], {}, b], h.v{h[**kw, &b] += 1; nil}) + assert_equal([[0], {}, b, [0, 4], {}, b], h.v{h[0, **kw, &b] += 1; nil}) + assert_equal([[0], {}, b, [0, 4], {}, b], h.v{h[0, *a, **kw, &b] += 1; nil}) + assert_equal([[], {kw: 5}, b, [4], {kw: 5}, b], h.v{h[kw: 5, &b] += 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b, [4], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] += 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b, [4], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] += 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, b, [0, 4], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] += 1; nil}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b, [0, 4], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1; nil}) + + # &&=, without block, non-popped + assert_equal([[], {}, nil, [1], {}, nil], h.v{h[**kw] &&= 1}) + assert_equal([[0], {}, nil, [0, 1], {}, nil], h.v{h[0, **kw] &&= 1}) + assert_equal([[0], {}, nil, [0, 1], {}, nil], h.v{h[0, *a, **kw] &&= 1}) + assert_equal([[], {kw: 5}, nil, [1], {kw: 5}, nil], h.v{h[kw: 5] &&= 1}) + assert_equal([[], {kw: 5, a: 2}, nil, [1], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] &&= 1}) + assert_equal([[], {kw: 5, a: 2}, nil, [1], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] &&= 1}) + assert_equal([[0], {kw: 5, a: 2}, nil, [0, 1], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] &&= 1}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil, [0, 1], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1}) + + # &&=, with block, non-popped + assert_equal([[], {}, b, [1], {}, b], h.v{h[**kw, &b] &&= 1}) + assert_equal([[0], {}, b, [0, 1], {}, b], h.v{h[0, **kw, &b] &&= 1}) + assert_equal([[0], {}, b, [0, 1], {}, b], h.v{h[0, *a, **kw, &b] &&= 1}) + assert_equal([[], {kw: 5}, b, [1], {kw: 5}, b], h.v{h[kw: 5, &b] &&= 1}) + assert_equal([[], {kw: 5, a: 2}, b, [1], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] &&= 1}) + assert_equal([[], {kw: 5, a: 2}, b, [1], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] &&= 1}) + assert_equal([[0], {kw: 5, a: 2}, b, [0, 1], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] &&= 1}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b, [0, 1], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1}) + + # &&=, without block, popped + assert_equal([[], {}, nil, [1], {}, nil], h.v{h[**kw] &&= 1; nil}) + assert_equal([[0], {}, nil, [0, 1], {}, nil], h.v{h[0, **kw] &&= 1; nil}) + assert_equal([[0], {}, nil, [0, 1], {}, nil], h.v{h[0, *a, **kw] &&= 1; nil}) + assert_equal([[], {kw: 5}, nil, [1], {kw: 5}, nil], h.v{h[kw: 5] &&= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil, [1], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] &&= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil, [1], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] &&= 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, nil, [0, 1], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] &&= 1; nil}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil, [0, 1], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1; nil}) + + # &&=, with block, popped + assert_equal([[], {}, b, [1], {}, b], h.v{h[**kw, &b] &&= 1; nil}) + assert_equal([[0], {}, b, [0, 1], {}, b], h.v{h[0, **kw, &b] &&= 1; nil}) + assert_equal([[0], {}, b, [0, 1], {}, b], h.v{h[0, *a, **kw, &b] &&= 1; nil}) + assert_equal([[], {kw: 5}, b, [1], {kw: 5}, b], h.v{h[kw: 5, &b] &&= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b, [1], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] &&= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b, [1], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] &&= 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, b, [0, 1], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] &&= 1; nil}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b, [0, 1], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1; nil}) + + # ||=, without block, non-popped + assert_equal([[], {}, nil], h.v{h[**kw] ||= 1}) + assert_equal([[0], {}, nil], h.v{h[0, **kw] ||= 1}) + assert_equal([[0], {}, nil], h.v{h[0, *a, **kw] ||= 1}) + assert_equal([[], {kw: 5}, nil], h.v{h[kw: 5] ||= 1}) + assert_equal([[], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] ||= 1}) + assert_equal([[], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] ||= 1}) + assert_equal([[0], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] ||= 1}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1}) + + # ||=, with block, non-popped + assert_equal([[], {}, b], h.v{h[**kw, &b] ||= 1}) + assert_equal([[0], {}, b], h.v{h[0, **kw, &b] ||= 1}) + assert_equal([[0], {}, b], h.v{h[0, *a, **kw, &b] ||= 1}) + assert_equal([[], {kw: 5}, b], h.v{h[kw: 5, &b] ||= 1}) + assert_equal([[], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] ||= 1}) + assert_equal([[], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] ||= 1}) + assert_equal([[0], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] ||= 1}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1}) + + # ||=, without block, popped + assert_equal([[], {}, nil], h.v{h[**kw] ||= 1; nil}) + assert_equal([[0], {}, nil], h.v{h[0, **kw] ||= 1; nil}) + assert_equal([[0], {}, nil], h.v{h[0, *a, **kw] ||= 1; nil}) + assert_equal([[], {kw: 5}, nil], h.v{h[kw: 5] ||= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] ||= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] ||= 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] ||= 1; nil}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1; nil}) + + # ||=, with block, popped + assert_equal([[], {}, b], h.v{h[**kw, &b] ||= 1; nil}) + assert_equal([[0], {}, b], h.v{h[0, **kw, &b] ||= 1; nil}) + assert_equal([[0], {}, b], h.v{h[0, *a, **kw, &b] ||= 1; nil}) + assert_equal([[], {kw: 5}, b], h.v{h[kw: 5, &b] ||= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] ||= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] ||= 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] ||= 1; nil}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1; nil}) + + end + + def test_kwsplat_block_order_op_asgn + o = Object.new + ary = [] + o.define_singleton_method(:to_a) {ary << :to_a; []} + o.define_singleton_method(:to_hash) {ary << :to_hash; {}} + o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}} + + def o.[](...) 2 end + def o.[]=(...) end + + o[kw: 1] += 1 + assert_equal([], ary) + + o[**o] += 1 + assert_equal([:to_hash], ary) + + ary.clear + o[**o, &o] += 1 + # to_proc called twice because no VM instruction for coercing to proc + assert_equal([:to_hash, :to_proc, :to_proc], ary) + + ary.clear + o[*o, **o, &o] += 1 + assert_equal([:to_a, :to_hash, :to_proc, :to_proc], ary) + end + def test_call_splat_order bug12860 = '[ruby-core:77701] [Bug# 12860]' ary = [1, 2] |