ruby/test/ruby/test_allocation.rb
Jeremy Evans ea7ceff82c
Avoid hash allocation for certain proc calls
Previously, proc calls such as:

```ruby
proc{|| }.(**empty_hash)
proc{|b: 1| }.(**r2k_array_with_empty_hash)
```

both allocated hashes unnecessarily, due to two separate code paths.

The first call goes through CALLER_SETUP_ARG/vm_caller_setup_keyword_hash,
and is simple to fix by not duping an empty keyword hash that will be
dropped.

The second case is more involved, in setup_parameters_complex, but is
fixed the exact same way as when the ruby2_keywords hash is not empty,
by flattening the rest array to the VM stack, ignoring the last
element (the empty keyword splat).  Add a flatten_rest_array static
function to handle this case.

Update test_allocation.rb to automatically convert the method call
allocation tests to proc allocation tests, at least for the calls
that can be converted.  With the code changes, all proc call
allocation tests pass, showing that proc calls and method calls
now allocate the same number of objects.

I've audited the allocation tests, and I believe that all of the low
hanging fruit has been collected.  All remaining allocations are
either caller side:

* Positional splat + post argument
* Multiple positional splats
* Literal keywords + keyword splat
* Multiple keyword splats

Or callee side:

* Positional splat parameter
* Keyword splat parameter
* Keyword to positional argument conversion for methods that don't accept keywords
* ruby2_keywords method called with keywords

Reapplies abc04e898b, which was reverted at
d56470a27c, with the addition of a bug fix and
test.

Fixes [Bug #20679]
2024-08-19 19:00:37 -07:00

842 lines
45 KiB
Ruby

# frozen_string_literal: false
require 'test/unit'
class TestAllocation < Test::Unit::TestCase
def munge_checks(checks)
checks
end
def check_allocations(checks)
dups = checks.split("\n").reject(&:empty?).tally.select{|_,v| v > 1}
raise "duplicate checks:\n#{dups.keys.join("\n")}" unless dups.empty?
checks = munge_checks(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]
r2k_array = r2k_array = [Hash.ruby2_keywords_hash(a: 3)]
r2k_array1 = r2k_array1 = [1, Hash.ruby2_keywords_hash(a: 3)]
r2k_empty_array = r2k_empty_array = [Hash.ruby2_keywords_hash({})]
r2k_empty_array1 = r2k_empty_array1 = [1, Hash.ruby2_keywords_hash({})]
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})")
check_allocations(0, 0, "none(*r2k_empty_array#{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})")
check_allocations(0, 0, "required(*r2k_empty_array1#{block})")
check_allocations(0, 1, "required(*r2k_array#{block})")
check_allocations(0, 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})")
check_allocations(0, 0, "optional(*r2k_empty_array#{block})")
check_allocations(0, 0, "optional(*r2k_empty_array1#{block})")
check_allocations(0, 1, "optional(*r2k_array#{block})")
check_allocations(0, 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, 1, "splat(**hash1#{block})")
check_allocations(1, 1, "splat(**hash1, **empty_hash#{block})")
check_allocations(1, 1, "splat(*empty_array, **hash1, **empty_hash#{block})")
check_allocations(1, 0, "splat(*r2k_empty_array#{block})")
check_allocations(1, 0, "splat(*r2k_empty_array1#{block})")
check_allocations(1, 1, "splat(*r2k_array#{block})")
check_allocations(1, 1, "splat(*r2k_array1#{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, 1, "req_splat(**hash1#{block})")
check_allocations(1, 1, "req_splat(**hash1, **empty_hash#{block})")
check_allocations(1, 1, "req_splat(*empty_array, **hash1, **empty_hash#{block})")
check_allocations(1, 0, "req_splat(*r2k_empty_array1#{block})")
check_allocations(1, 1, "req_splat(*r2k_array#{block})")
check_allocations(1, 1, "req_splat(*r2k_array1#{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, 1, "splat_post(**hash1#{block})")
check_allocations(1, 1, "splat_post(**hash1, **empty_hash#{block})")
check_allocations(1, 1, "splat_post(*empty_array, **hash1, **empty_hash#{block})")
check_allocations(1, 0, "splat_post(*r2k_empty_array1#{block})")
check_allocations(1, 1, "splat_post(*r2k_array#{block})")
check_allocations(1, 1, "splat_post(*r2k_array1#{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(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})")
check_allocations(0, 0, "keyword(*r2k_empty_array#{block})")
check_allocations(0, 0, "keyword(*r2k_array#{block})")
check_allocations(0, 1, "keyword(*empty_array, a: 2, **empty_hash#{block})")
check_allocations(0, 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(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
check_allocations(0, 1, "keyword_splat(*r2k_empty_array#{block})")
check_allocations(0, 1, "keyword_splat(*r2k_array#{block})")
check_allocations(0, 1, "keyword_splat(*empty_array, a: 2, **empty_hash#{block})")
check_allocations(0, 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(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
check_allocations(0, 1, "keyword_and_keyword_splat(*r2k_empty_array#{block})")
check_allocations(0, 1, "keyword_and_keyword_splat(*r2k_array#{block})")
check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, a: 2, **empty_hash#{block})")
check_allocations(0, 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(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})")
check_allocations(0, 0, "required_and_keyword(*r2k_empty_array1#{block})")
check_allocations(0, 0, "required_and_keyword(*r2k_array1#{block})")
check_allocations(0, 1, "required_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(0, 1, "required_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "required_and_keyword(*array1, **empty_hash, a: 2#{block})")
check_allocations(0, 1, "required_and_keyword(*array1, **hash1, **empty_hash#{block})")
check_allocations(0, 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, 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})")
check_allocations(1, 0, "splat_and_keyword(*r2k_empty_array#{block})")
check_allocations(1, 0, "splat_and_keyword(*r2k_array#{block})")
check_allocations(1, 0, "splat_and_keyword(*r2k_empty_array1#{block})")
check_allocations(1, 0, "splat_and_keyword(*r2k_array1#{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(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})")
check_allocations(0, 1, "required_and_keyword_splat(*r2k_empty_array1#{block})")
check_allocations(0, 1, "required_and_keyword_splat(*r2k_array1#{block})")
check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "required_and_keyword_splat(*array1, **empty_hash, a: 2#{block})")
check_allocations(0, 1, "required_and_keyword_splat(*array1, **hash1, **empty_hash#{block})")
check_allocations(0, 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, *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})")
check_allocations(1, 1, "splat_and_keyword_splat(*r2k_empty_array#{block})")
check_allocations(1, 1, "splat_and_keyword_splat(*r2k_array#{block})")
check_allocations(1, 1, "splat_and_keyword_splat(*r2k_empty_array1#{block})")
check_allocations(1, 1, "splat_and_keyword_splat(*r2k_array1#{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, 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(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})")
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array1#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array1#{block})")
RUBY
end
def test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters
check_allocations(<<~RUBY)
def self.t(*, **#{block}); end
def self.anon_splat_and_anon_keyword_splat(*, **#{block}); t(*, **) 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, 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(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})")
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array1#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array1#{block})")
RUBY
end
def test_argument_forwarding
check_allocations(<<~RUBY)
def self.argument_forwarding(...); end
check_allocations(0, 0, "argument_forwarding(1, a: 2#{block})")
check_allocations(0, 0, "argument_forwarding(1, *empty_array, a: 2#{block})")
check_allocations(0, 1, "argument_forwarding(1, a:2, **empty_hash#{block})")
check_allocations(0, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})")
check_allocations(0, 0, "argument_forwarding(1, **nil#{block})")
check_allocations(0, 0, "argument_forwarding(1, **empty_hash#{block})")
check_allocations(0, 0, "argument_forwarding(1, **hash1#{block})")
check_allocations(0, 0, "argument_forwarding(1, *empty_array, **hash1#{block})")
check_allocations(0, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})")
check_allocations(0, 0, "argument_forwarding(1, *empty_array#{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(0, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(0, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})")
check_allocations(0, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})")
check_allocations(0, 0, "argument_forwarding(*array1, **nil#{block})")
check_allocations(0, 0, "argument_forwarding(*r2k_empty_array#{block})")
check_allocations(0, 0, "argument_forwarding(*r2k_array#{block})")
check_allocations(0, 0, "argument_forwarding(*r2k_empty_array1#{block})")
check_allocations(0, 0, "argument_forwarding(*r2k_array1#{block})")
RUBY
end
def test_nested_argument_forwarding
check_allocations(<<~RUBY)
def self.t(...) end
def self.argument_forwarding(...); t(...) end
check_allocations(0, 0, "argument_forwarding(1, a: 2#{block})")
check_allocations(0, 0, "argument_forwarding(1, *empty_array, a: 2#{block})")
check_allocations(0, 1, "argument_forwarding(1, a:2, **empty_hash#{block})")
check_allocations(0, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})")
check_allocations(0, 0, "argument_forwarding(1, **nil#{block})")
check_allocations(0, 0, "argument_forwarding(1, **empty_hash#{block})")
check_allocations(0, 0, "argument_forwarding(1, **hash1#{block})")
check_allocations(0, 0, "argument_forwarding(1, *empty_array, **hash1#{block})")
check_allocations(0, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})")
check_allocations(0, 0, "argument_forwarding(1, *empty_array#{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(0, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(0, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})")
check_allocations(0, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})")
check_allocations(0, 0, "argument_forwarding(*array1, **nil#{block})")
check_allocations(0, 0, "argument_forwarding(*r2k_empty_array#{block})")
check_allocations(0, 0, "argument_forwarding(*r2k_array#{block})")
check_allocations(0, 0, "argument_forwarding(*r2k_empty_array1#{block})")
check_allocations(0, 0, "argument_forwarding(*r2k_array1#{block})")
RUBY
end
def test_ruby2_keywords
check_allocations(<<~RUBY)
def self.r2k(*a#{block}); end
singleton_class.send(:ruby2_keywords, :r2k)
check_allocations(1, 1, "r2k(1, a: 2#{block})")
check_allocations(1, 1, "r2k(1, *empty_array, a: 2#{block})")
check_allocations(1, 1, "r2k(1, a:2, **empty_hash#{block})")
check_allocations(1, 1, "r2k(1, **empty_hash, a: 2#{block})")
check_allocations(1, 0, "r2k(1, **nil#{block})")
check_allocations(1, 0, "r2k(1, **empty_hash#{block})")
check_allocations(1, 1, "r2k(1, **hash1#{block})")
check_allocations(1, 1, "r2k(1, *empty_array, **hash1#{block})")
check_allocations(1, 1, "r2k(1, **hash1, **empty_hash#{block})")
check_allocations(1, 1, "r2k(1, **empty_hash, **hash1#{block})")
check_allocations(1, 0, "r2k(1, *empty_array#{block})")
check_allocations(1, 0, "r2k(1, *empty_array, *empty_array, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, a: 2#{block})")
check_allocations(1, 0, "r2k(*array1, **nill#{block})")
check_allocations(1, 0, "r2k(*array1, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, **hash1#{block})")
check_allocations(1, 1, "r2k(*array1, *empty_array, **hash1#{block})")
check_allocations(1, 0, "r2k(*array1, *empty_array#{block})")
check_allocations(1, 0, "r2k(*array1, *empty_array, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, *empty_array, **hash1, **empty_hash#{block})")
check_allocations(1, 1, "r2k(1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(1, 1, "r2k(1, *empty_array, **hash1, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, **empty_hash, a: 2#{block})")
check_allocations(1, 1, "r2k(*array1, **hash1, **empty_hash#{block})")
check_allocations(1, 0, "r2k(*array1, **nil#{block})")
check_allocations(1, 0, "r2k(*r2k_empty_array#{block})")
check_allocations(1, 1, "r2k(*r2k_array#{block})")
unless defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
# YJIT may or may not allocate depending on arch?
check_allocations(1, 0, "r2k(*r2k_empty_array1#{block})")
check_allocations(1, 1, "r2k(*r2k_array1#{block})")
end
RUBY
end
def test_no_array_allocation_with_splat_and_nonstatic_keywords
check_allocations(<<~RUBY)
def self.keyword(a: nil, b: nil#{block}); end
check_allocations(0, 1, "keyword(*empty_array, a: empty_array#{block})") # LVAR
check_allocations(0, 1, "->{keyword(*empty_array, a: empty_array#{block})}.call") # DVAR
check_allocations(0, 1, "$x = empty_array; keyword(*empty_array, a: $x#{block})") # GVAR
check_allocations(0, 1, "@x = empty_array; keyword(*empty_array, a: @x#{block})") # IVAR
check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword(*empty_array, a: X#{block})") # CONST
check_allocations(0, 1, "keyword(*empty_array, a: Object::X#{block})") # COLON2
check_allocations(0, 1, "keyword(*empty_array, a: ::X#{block})") # COLON3
check_allocations(0, 1, "T = self; #{'B = block' unless block.empty?}; class Object; @@x = X; T.keyword(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1#{block})") # INTEGER
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1.0#{block})") # FLOAT
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1.0r#{block})") # RATIONAL
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1.0i#{block})") # IMAGINARY
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 'a'#{block})") # STR
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: :b#{block})") # SYM
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: /a/#{block})") # REGX
check_allocations(0, 1, "keyword(*empty_array, a: self#{block})") # SELF
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: nil#{block})") # NIL
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: true#{block})") # TRUE
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: false#{block})") # FALSE
check_allocations(0, 1, "keyword(*empty_array, a: ->{}#{block})") # LAMBDA
check_allocations(0, 1, "keyword(*empty_array, a: $1#{block})") # NTH_REF
check_allocations(0, 1, "keyword(*empty_array, a: $`#{block})") # BACK_REF
RUBY
end
class WithBlock < self
def block
', &block'
end
end
end
class ProcCall < MethodCall
def munge_checks(checks)
return checks if @no_munge
sub = rep = nil
checks.split("\n").map do |line|
case line
when "singleton_class.send(:ruby2_keywords, :r2k)"
"r2k.ruby2_keywords"
when /\Adef self.([a-z0-9_]+)\((.*)\);(.*)end\z/
sub = $1 + '('
rep = $1 + '.('
"#{$1} = #{$1} = proc{ |#{$2}| #{$3} }"
when /check_allocations/
line.gsub(sub, rep)
else
line
end
end.join("\n")
end
# Generic argument forwarding not supported in proc definitions
undef_method :test_argument_forwarding
undef_method :test_nested_argument_forwarding
# Proc anonymous arguments cannot be used directly
undef_method :test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters
def test_no_array_allocation_with_splat_and_nonstatic_keywords
@no_munge = true
check_allocations(<<~RUBY)
keyword = keyword = proc{ |a: nil, b: nil #{block}| }
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array#{block})") # LVAR
check_allocations(0, 1, "->{keyword.(*empty_array, a: empty_array#{block})}.call") # DVAR
check_allocations(0, 1, "$x = empty_array; keyword.(*empty_array, a: $x#{block})") # GVAR
check_allocations(0, 1, "@x = empty_array; keyword.(*empty_array, a: @x#{block})") # IVAR
check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword.(*empty_array, a: X#{block})") # CONST
check_allocations(0, 1, "keyword.(*empty_array, a: Object::X#{block})") # COLON2
check_allocations(0, 1, "keyword.(*empty_array, a: ::X#{block})") # COLON3
check_allocations(0, 1, "T = keyword; #{'B = block' unless block.empty?}; class Object; @@x = X; T.(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1#{block})") # INTEGER
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1.0#{block})") # FLOAT
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1.0r#{block})") # RATIONAL
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1.0i#{block})") # IMAGINARY
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 'a'#{block})") # STR
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: :b#{block})") # SYM
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: /a/#{block})") # REGX
check_allocations(0, 1, "keyword.(*empty_array, a: self#{block})") # SELF
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: nil#{block})") # NIL
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: true#{block})") # TRUE
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: false#{block})") # FALSE
check_allocations(0, 1, "keyword.(*empty_array, a: ->{}#{block})") # LAMBDA
check_allocations(0, 1, "keyword.(*empty_array, a: $1#{block})") # NTH_REF
check_allocations(0, 1, "keyword.(*empty_array, a: $`#{block})") # BACK_REF
RUBY
end
class WithBlock < self
def block
', &block'
end
end
end
end