summaryrefslogtreecommitdiff
path: root/test/ruby
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2024-03-22 13:53:35 -0700
committerJeremy Evans <code@jeremyevans.net>2024-03-27 07:12:35 -0700
commite4d6479730fa87fc420cfe9ae6b83206737b0db5 (patch)
tree7ecfc2a24467c85b51da44bb2c0b841d652b6d0d /test/ruby
parentaa794cc5a237bf4b7aa8f1917fa4ed86949d71c6 (diff)
Add array/hash implicit allocation tests
These are designed to prevent allocation regressions (commits that increase the number of implicitly allocated arrays and hashes). We have already had three commits in the last couple weeks to fix allocation regressions: * 15dc3aaa311b32203d8ffb414bcf9b8e55ce5691 * aceee71c35e0b387691836e756b4e008efd84cf1 * c38878494377c94f2425a81e598260ea944ef7f3 This test suite should hopefully allow us to find such regressions in CI before commit, to avoid committing future allocation regressions. This uses assert_separately around each set of tests. Doing it for each individual check was too slow. Failures are gathered and reported at the end of the the suite as a single assertion, with the message describing all failures.
Diffstat (limited to 'test/ruby')
-rw-r--r--test/ruby/test_allocation.rb656
1 files changed, 656 insertions, 0 deletions
diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb
new file mode 100644
index 0000000000..48348c0fbd
--- /dev/null
+++ b/test/ruby/test_allocation.rb
@@ -0,0 +1,656 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestAllocation < Test::Unit::TestCase
+ def check_allocations(checks)
+ assert_separately([], <<~RUBY)
+ $allocations = [0, 0]
+ $counts = {}
+ failures = []
+
+ def self.num_allocations
+ ObjectSpace.count_objects($counts)
+ arrays = $counts[:T_ARRAY]
+ hashes = $counts[:T_HASH]
+ yield
+ ObjectSpace.count_objects($counts)
+ arrays -= $counts[:T_ARRAY]
+ hashes -= $counts[:T_HASH]
+ $allocations[0] = -arrays
+ $allocations[1] = -hashes
+ end
+
+ define_singleton_method(:check_allocations) do |num_arrays, num_hashes, check_code|
+ instance_eval <<~RB
+ empty_array = empty_array = []
+ empty_hash = empty_hash = {}
+ array1 = array1 = [1]
+ hash1 = hash1 = {a: 2}
+ nill = nill = nil
+ block = block = lambda{}
+
+ num_allocations do
+ \#{check_code}
+ end
+ RB
+
+ if num_arrays != $allocations[0]
+ failures << "Expected \#{num_arrays} array allocations for \#{check_code.inspect}, but \#{$allocations[0]} arrays allocated"
+ end
+ if num_hashes != $allocations[1]
+ failures << "Expected \#{num_hashes} hash allocations for \#{check_code.inspect}, but \#{$allocations[1]} hashes allocated"
+ end
+ end
+
+ GC.start
+ GC.disable
+
+ #{checks}
+
+ unless failures.empty?
+ assert_equal(true, false, failures.join("\n"))
+ end
+ RUBY
+ end
+
+ class Literal < self
+ def test_array_literal
+ check_allocations(<<~RUBY)
+ check_allocations(1, 0, "[]")
+ check_allocations(1, 0, "[1]")
+ check_allocations(1, 0, "[*empty_array]")
+ check_allocations(1, 0, "[*empty_array, 1, *empty_array]")
+ check_allocations(1, 0, "[*empty_array, *empty_array]")
+ check_allocations(1, 0, "[#{'1,'*100000}]")
+ RUBY
+ end
+
+ def test_hash_literal
+ check_allocations(<<~RUBY)
+ check_allocations(0, 1, "{}")
+ check_allocations(0, 1, "{a: 1}")
+ check_allocations(0, 1, "{**empty_hash}")
+ check_allocations(0, 1, "{**empty_hash, a: 1, **empty_hash}")
+ check_allocations(0, 1, "{**empty_hash, **empty_hash}")
+ check_allocations(0, 1, "{#{100000.times.map{|i| "a#{i}: 1"}.join(',')}}")
+ RUBY
+ end
+ end
+
+ class MethodCall < self
+ def block
+ ''
+ end
+
+ def test_no_parameters
+ only_block = block.empty? ? block : block[2..]
+ check_allocations(<<~RUBY)
+ def self.none(#{only_block}); end
+
+ check_allocations(0, 0, "none(#{only_block})")
+ check_allocations(0, 0, "none(*empty_array#{block})")
+ check_allocations(0, 0, "none(**empty_hash#{block})")
+ check_allocations(0, 0, "none(*empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "none(*empty_array, *empty_array#{block})")
+ check_allocations(0, 1, "none(**empty_hash, **empty_hash#{block})")
+ check_allocations(1, 1, "none(*empty_array, *empty_array, **empty_hash, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_required_parameter
+ check_allocations(<<~RUBY)
+ def self.required(x#{block}); end
+
+ check_allocations(0, 0, "required(1#{block})")
+ check_allocations(0, 0, "required(1, *empty_array#{block})")
+ check_allocations(0, 0, "required(1, **empty_hash#{block})")
+ check_allocations(0, 0, "required(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "required(*array1#{block})")
+ check_allocations(0, 1, "required(**hash1#{block})")
+
+ check_allocations(1, 0, "required(*array1, *empty_array#{block})")
+ check_allocations(0, 1, "required(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "required(*array1, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "required(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_optional_parameter
+ check_allocations(<<~RUBY)
+ def self.optional(x=nil#{block}); end
+
+ check_allocations(0, 0, "optional(1#{block})")
+ check_allocations(0, 0, "optional(1, *empty_array#{block})")
+ check_allocations(0, 0, "optional(1, **empty_hash#{block})")
+ check_allocations(0, 0, "optional(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "optional(*array1#{block})")
+ check_allocations(0, 1, "optional(**hash1#{block})")
+
+ check_allocations(1, 0, "optional(*array1, *empty_array#{block})")
+ check_allocations(0, 1, "optional(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "optional(*array1, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "optional(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.splat(*x#{block}); end
+
+ check_allocations(1, 0, "splat(1#{block})")
+ check_allocations(1, 0, "splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(*array1#{block})")
+ check_allocations(1, 0, "splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(1, *array1#{block})")
+ check_allocations(1, 0, "splat(1, *array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat(1, *array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(1, *array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(*array1#{block})")
+ check_allocations(1, 1, "splat(**hash1#{block})")
+
+ check_allocations(1, 0, "splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(*array1, *empty_array, **empty_hash#{block})")
+ check_allocations(1, 1, "splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_required_and_positional_splat_parameters
+ check_allocations(<<~RUBY)
+ def self.req_splat(x, *y#{block}); end
+
+ check_allocations(1, 0, "req_splat(1#{block})")
+ check_allocations(1, 0, "req_splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "req_splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "req_splat(*array1#{block})")
+ check_allocations(1, 0, "req_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "req_splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "req_splat(1, *array1#{block})")
+ check_allocations(1, 0, "req_splat(1, *array1, *empty_array#{block})")
+ check_allocations(1, 0, "req_splat(1, *array1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(1, *array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "req_splat(*array1#{block})")
+ check_allocations(1, 1, "req_splat(**hash1#{block})")
+
+ check_allocations(1, 0, "req_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "req_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(*array1, *empty_array, **empty_hash#{block})")
+ check_allocations(1, 1, "req_splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_and_post_parameters
+ check_allocations(<<~RUBY)
+ def self.splat_post(*x, y#{block}); end
+
+ check_allocations(1, 0, "splat_post(1#{block})")
+ check_allocations(1, 0, "splat_post(1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_post(1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_post(*array1#{block})")
+ check_allocations(1, 0, "splat_post(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_post(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_post(1, *array1#{block})")
+ check_allocations(1, 0, "splat_post(1, *array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_post(1, *array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(1, *array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_post(*array1#{block})")
+ check_allocations(1, 1, "splat_post(**hash1#{block})")
+
+ check_allocations(1, 0, "splat_post(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_post(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(*array1, *empty_array, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_post(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_keyword_parameter
+ check_allocations(<<~RUBY)
+ def self.keyword(a: nil#{block}); end
+
+ check_allocations(0, 0, "keyword(a: 2#{block})")
+ check_allocations(0, 0, "keyword(*empty_array, a: 2#{block})")
+ check_allocations(0, 1, "keyword(a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword(**empty_hash, a: 2#{block})")
+
+ check_allocations(0, 0, "keyword(**nil#{block})")
+ check_allocations(0, 0, "keyword(**empty_hash#{block})")
+ check_allocations(0, 0, "keyword(**hash1#{block})")
+ check_allocations(0, 0, "keyword(*empty_array, **hash1#{block})")
+ check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword(**empty_hash, **hash1#{block})")
+
+ check_allocations(0, 0, "keyword(*empty_array#{block})")
+ check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "keyword(*empty_array#{block})")
+ check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "keyword(*empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.keyword_splat(**kw#{block}); end
+
+ check_allocations(0, 1, "keyword_splat(a: 2#{block})")
+ check_allocations(0, 1, "keyword_splat(*empty_array, a: 2#{block})")
+ check_allocations(0, 1, "keyword_splat(a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(**empty_hash, a: 2#{block})")
+
+ check_allocations(0, 1, "keyword_splat(**nil#{block})")
+ check_allocations(0, 1, "keyword_splat(**empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1#{block})")
+ check_allocations(0, 1, "keyword_splat(*empty_array, **hash1#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(**empty_hash, **hash1#{block})")
+
+ check_allocations(0, 1, "keyword_splat(*empty_array#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 1, "keyword_splat(*empty_array#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "keyword_splat(*empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_keyword_and_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.keyword_and_keyword_splat(a: 1, **kw#{block}); end
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(a: 2#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, a: 2#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, a: 2#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(**nil#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, **hash1#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, **hash1#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_required_positional_and_keyword_parameter
+ check_allocations(<<~RUBY)
+ def self.required_and_keyword(b, a: nil#{block}); end
+
+ check_allocations(0, 0, "required_and_keyword(1, a: 2#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, *empty_array, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(1, **nil#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, **empty_hash#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, **hash1#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, *empty_array, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(1, *empty_array#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "required_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(*array1, **nill#{block})")
+ check_allocations(0, 0, "required_and_keyword(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "required_and_keyword(*array1, **hash1#{block})")
+ check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "required_and_keyword(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "required_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "required_and_keyword(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "required_and_keyword(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_and_keyword_parameter
+ check_allocations(<<~RUBY)
+ def self.splat_and_keyword(*b, a: nil#{block}); end
+
+ check_allocations(1, 0, "splat_and_keyword(1, a: 2#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(1, **nil#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, **hash1#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*array1, a: 2#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*array1, **nill#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, **hash1#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_required_and_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.required_and_keyword_splat(b, **kw#{block}); end
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, **nil#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **nill#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_and_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.splat_and_keyword_splat(*b, **kw#{block}); end
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **nil#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nill#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_anonymous_splat_and_anonymous_keyword_splat_parameters
+ check_allocations(<<~RUBY)
+ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); end
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters
+ check_allocations(<<~RUBY)
+ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); t(*, **) end; def self.t(*, **#{block}); end
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_argument_forwarding
+ check_allocations(<<~RUBY)
+ def self.argument_forwarding(...); end
+
+ check_allocations(1, 1, "argument_forwarding(1, a: 2#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(1, **nil#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_nested_argument_forwarding
+ check_allocations(<<~RUBY)
+ def self.argument_forwarding(...); t(...) end; def self.t(...) end
+
+ check_allocations(1, 1, "argument_forwarding(1, a: 2#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(1, **nil#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, **nil#{block})")
+ RUBY
+ end
+
+ class WithBlock < self
+ def block
+ ', &block'
+ end
+ end
+ end
+end