summaryrefslogtreecommitdiff
path: root/spec/ruby
diff options
context:
space:
mode:
authorJeremy Evans <[email protected]>2025-01-18 01:40:05 -0800
committerJeremy Evans <[email protected]>2025-03-27 11:17:40 -0700
commit67d1dd2ebd622c27d2ae0681c544d9f5d2f5349b (patch)
tree2705052203d04d21b31a61f7b1ebac01fb30a484 /spec/ruby
parent6ecfe643b5d8d64682c6f6bce5b27db5c007331d (diff)
Avoid array allocation for *nil, by not calling nil.to_a
The following method call: ```ruby a(*nil) ``` A method call such as `a(*nil)` previously allocated an array, because it calls `nil.to_a`, but I have determined this array allocation is unnecessary. The instructions in this case are: ``` 0000 putself ( 1)[Li] 0001 putnil 0002 splatarray false 0004 opt_send_without_block <calldata!mid:a, argc:1, ARGS_SPLAT|FCALL> 0006 leave ``` The method call uses `ARGS_SPLAT` without `ARGS_SPLAT_MUT`, so the returned array doesn't need to be mutable. I believe all cases where `splatarray false` are used allow the returned object to be frozen, since the `false` means to not duplicate the array. The optimization in this case is to have `splatarray false` push a shared empty frozen array, instead of calling `nil.to_a` to return a newly allocated array. There is a slightly backwards incompatibility with this optimization, in that `nil.to_a` is not called. However, I believe the new behavior of `*nil` not calling `nil.to_a` is more consistent with how `**nil` does not call `nil.to_hash`. Also, so much Ruby code would break if `nil.to_a` returned something different from the empty hash, that it's difficult to imagine anyone actually doing that in real code, though we have a few tests/specs for that. I think it would be bad for consistency if `*nil` called `nil.to_a` in some cases and not others, so this changes other cases to not call `nil.to_a`: For `[*nil]`, this uses `splatarray true`, which now allocates a new array for a `nil` argument without calling `nil.to_a`. For `[1, *nil]`, this uses `concattoarray`, which now returns the first array if the second array is `nil`. This updates the allocation tests to check that the array allocations are avoided where possible. Implements [Feature #21047]
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/12597
Diffstat (limited to 'spec/ruby')
-rw-r--r--spec/ruby/language/variables_spec.rb19
1 files changed, 15 insertions, 4 deletions
diff --git a/spec/ruby/language/variables_spec.rb b/spec/ruby/language/variables_spec.rb
index bd993acb0f..eb080eea55 100644
--- a/spec/ruby/language/variables_spec.rb
+++ b/spec/ruby/language/variables_spec.rb
@@ -363,11 +363,22 @@ describe "Multiple assignment" do
a.should == []
end
- it "calls #to_a to convert nil to an empty Array" do
- nil.should_receive(:to_a).and_return([])
+ ruby_version_is "3.5" do
+ it "converts nil to empty array without calling a method" do
+ nil.should_not_receive(:to_a)
- (*a = *nil).should == []
- a.should == []
+ (*a = *nil).should == []
+ a.should == []
+ end
+ end
+
+ ruby_version_is ""..."3.5" do
+ it "calls #to_a to convert nil to an empty Array" do
+ nil.should_receive(:to_a).and_return([])
+
+ (*a = *nil).should == []
+ a.should == []
+ end
end
it "does not call #to_a on an Array" do