2018-03-04 10:09:32 -05:00
|
|
|
require_relative '../spec_helper'
|
|
|
|
require_relative 'fixtures/send'
|
2017-05-07 08:04:49 -04:00
|
|
|
|
|
|
|
# Why so many fixed arg tests? JRuby and I assume other Ruby impls have
|
|
|
|
# separate call paths for simple fixed arity methods. Testing up to five
|
|
|
|
# will verify special and generic arity code paths for all impls.
|
|
|
|
#
|
|
|
|
# Method naming conventions:
|
2019-02-07 11:35:33 -05:00
|
|
|
# M - Mandatory Args
|
2017-05-07 08:04:49 -04:00
|
|
|
# O - Optional Arg
|
|
|
|
# R - Rest Arg
|
2019-02-07 11:35:33 -05:00
|
|
|
# Q - Post Mandatory Args
|
2017-05-07 08:04:49 -04:00
|
|
|
|
|
|
|
specs = LangSendSpecs
|
|
|
|
|
|
|
|
describe "Invoking a method" do
|
|
|
|
describe "with zero arguments" do
|
|
|
|
it "requires no arguments passed" do
|
|
|
|
specs.fooM0.should == 100
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises ArgumentError if the method has a positive arity" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
specs.fooM1
|
|
|
|
}.should raise_error(ArgumentError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "with only mandatory arguments" do
|
|
|
|
it "requires exactly the same number of passed values" do
|
|
|
|
specs.fooM1(1).should == [1]
|
|
|
|
specs.fooM2(1,2).should == [1,2]
|
|
|
|
specs.fooM3(1,2,3).should == [1,2,3]
|
|
|
|
specs.fooM4(1,2,3,4).should == [1,2,3,4]
|
|
|
|
specs.fooM5(1,2,3,4,5).should == [1,2,3,4,5]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises ArgumentError if the methods arity doesn't match" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
specs.fooM1(1,2)
|
|
|
|
}.should raise_error(ArgumentError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "with optional arguments" do
|
|
|
|
it "uses the optional argument if none is is passed" do
|
|
|
|
specs.fooM0O1.should == [1]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "uses the passed argument if available" do
|
|
|
|
specs.fooM0O1(2).should == [2]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises ArgumentError if extra arguments are passed" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
specs.fooM0O1(2,3)
|
|
|
|
}.should raise_error(ArgumentError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "with mandatory and optional arguments" do
|
|
|
|
it "uses the passed values in left to right order" do
|
|
|
|
specs.fooM1O1(2).should == [2,1]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises an ArgumentError if there are no values for the mandatory args" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
specs.fooM1O1
|
|
|
|
}.should raise_error(ArgumentError)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises an ArgumentError if too many values are passed" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
specs.fooM1O1(1,2,3)
|
|
|
|
}.should raise_error(ArgumentError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "with a rest argument" do
|
|
|
|
it "is an empty array if there are no additional arguments" do
|
|
|
|
specs.fooM0R().should == []
|
|
|
|
specs.fooM1R(1).should == [1, []]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "gathers unused arguments" do
|
|
|
|
specs.fooM0R(1).should == [1]
|
|
|
|
specs.fooM1R(1,2).should == [1, [2]]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with a block makes it available to yield" do
|
|
|
|
specs.oneb(10) { 200 }.should == [10,200]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with a block converts the block to a Proc" do
|
|
|
|
prc = specs.makeproc { "hello" }
|
|
|
|
prc.should be_kind_of(Proc)
|
|
|
|
prc.call.should == "hello"
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with an object as a block uses 'to_proc' for coercion" do
|
|
|
|
o = LangSendSpecs::ToProc.new(:from_to_proc)
|
|
|
|
|
|
|
|
specs.makeproc(&o).call.should == :from_to_proc
|
|
|
|
|
|
|
|
specs.yield_now(&o).should == :from_to_proc
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises a SyntaxError with both a literal block and an object as block" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
eval "specs.oneb(10, &l){ 42 }"
|
|
|
|
}.should raise_error(SyntaxError)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with same names as existing variables is ok" do
|
|
|
|
foobar = 100
|
|
|
|
|
|
|
|
def foobar; 200; end
|
|
|
|
|
|
|
|
foobar.should == 100
|
|
|
|
foobar().should == 200
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with splat operator makes the object the direct arguments" do
|
|
|
|
a = [1,2,3]
|
|
|
|
specs.fooM3(*a).should == [1,2,3]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "without parentheses works" do
|
|
|
|
(specs.fooM3 1,2,3).should == [1,2,3]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with a space separating method name and parenthesis treats expression in parenthesis as first argument" do
|
|
|
|
specs.weird_parens().should == "55"
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "allows []=" do
|
|
|
|
before :each do
|
|
|
|
@obj = LangSendSpecs::AttrSet.new
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with *args in the [] expanded to individual arguments" do
|
|
|
|
ary = [2,3]
|
|
|
|
(@obj[1, *ary] = 4).should == 4
|
|
|
|
@obj.result.should == [1,2,3,4]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with multiple *args" do
|
|
|
|
ary = [2,3]
|
|
|
|
post = [4,5]
|
|
|
|
(@obj[1, *ary] = *post).should == [4,5]
|
|
|
|
@obj.result.should == [1,2,3,[4,5]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with multiple *args and does not unwrap the last splat" do
|
|
|
|
ary = [2,3]
|
|
|
|
post = [4]
|
|
|
|
(@obj[1, *ary] = *post).should == [4]
|
|
|
|
@obj.result.should == [1,2,3,[4]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with a *args and multiple rhs args" do
|
|
|
|
ary = [2,3]
|
|
|
|
(@obj[1, *ary] = 4, 5).should == [4,5]
|
|
|
|
@obj.result.should == [1,2,3,[4,5]]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "passes literal hashes without curly braces as the last parameter" do
|
|
|
|
specs.fooM3('abc', 456, 'rbx' => 'cool',
|
|
|
|
'specs' => 'fail sometimes', 'oh' => 'weh').should == \
|
|
|
|
['abc', 456, {'rbx' => 'cool', 'specs' => 'fail sometimes', 'oh' => 'weh'}]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "passes a literal hash without curly braces or parens" do
|
|
|
|
(specs.fooM3 'abc', 456, 'rbx' => 'cool',
|
|
|
|
'specs' => 'fail sometimes', 'oh' => 'weh').should == \
|
|
|
|
['abc', 456, { 'rbx' => 'cool', 'specs' => 'fail sometimes', 'oh' => 'weh'}]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "allows to literal hashes without curly braces as the only parameter" do
|
|
|
|
specs.fooM1(rbx: :cool, specs: :fail_sometimes).should ==
|
|
|
|
[{ rbx: :cool, specs: :fail_sometimes }]
|
|
|
|
|
|
|
|
(specs.fooM1 rbx: :cool, specs: :fail_sometimes).should ==
|
|
|
|
[{ rbx: :cool, specs: :fail_sometimes }]
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "when the method is not available" do
|
|
|
|
it "invokes method_missing if it is defined" do
|
|
|
|
o = LangSendSpecs::MethodMissing.new
|
|
|
|
o.not_there(1,2)
|
|
|
|
o.message.should == :not_there
|
|
|
|
o.args.should == [1,2]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises NameError if invoked as a vcall" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { no_such_method }.should raise_error NameError
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
2018-06-13 17:58:54 -04:00
|
|
|
it "should omit the method_missing call from the backtrace for NameError" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { no_such_method }.should raise_error { |e| e.backtrace.first.should_not include("method_missing") }
|
2018-06-13 17:58:54 -04:00
|
|
|
end
|
|
|
|
|
2017-05-07 08:04:49 -04:00
|
|
|
it "raises NoMethodError if invoked as an unambiguous method call" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { no_such_method() }.should raise_error NoMethodError
|
|
|
|
-> { no_such_method(1,2,3) }.should raise_error NoMethodError
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
2018-06-13 17:58:54 -04:00
|
|
|
|
|
|
|
it "should omit the method_missing call from the backtrace for NoMethodError" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { no_such_method() }.should raise_error { |e| e.backtrace.first.should_not include("method_missing") }
|
2018-06-13 17:58:54 -04:00
|
|
|
end
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "Invoking a public setter method" do
|
|
|
|
it 'returns the set value' do
|
|
|
|
klass = Class.new do
|
|
|
|
def foobar=(*)
|
|
|
|
1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
(klass.new.foobar = 'bar').should == 'bar'
|
|
|
|
(klass.new.foobar = 'bar', 'baz').should == ["bar", "baz"]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "Invoking []= methods" do
|
|
|
|
it 'returns the set value' do
|
|
|
|
klass = Class.new do
|
|
|
|
def []=(*)
|
|
|
|
1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
(klass.new[33] = 'bar').should == 'bar'
|
|
|
|
(klass.new[33] = 'bar', 'baz').should == ['bar', 'baz']
|
|
|
|
(klass.new[33, 34] = 'bar', 'baz').should == ['bar', 'baz']
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "Invoking a private setter method" do
|
|
|
|
describe "permits self as a receiver" do
|
|
|
|
it "for normal assignment" do
|
|
|
|
receiver = LangSendSpecs::PrivateSetter.new
|
|
|
|
receiver.call_self_foo_equals(42)
|
|
|
|
receiver.foo.should == 42
|
|
|
|
end
|
|
|
|
|
|
|
|
it "for multiple assignment" do
|
|
|
|
receiver = LangSendSpecs::PrivateSetter.new
|
|
|
|
receiver.call_self_foo_equals_masgn(42)
|
|
|
|
receiver.foo.should == 42
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "Invoking a private getter method" do
|
2019-09-19 13:16:17 -04:00
|
|
|
ruby_version_is ""..."2.7" do
|
|
|
|
it "does not permit self as a receiver" do
|
|
|
|
receiver = LangSendSpecs::PrivateGetter.new
|
|
|
|
-> { receiver.call_self_foo }.should raise_error(NoMethodError)
|
|
|
|
-> { receiver.call_self_foo_or_equals(6) }.should raise_error(NoMethodError)
|
|
|
|
end
|
|
|
|
end
|
2019-09-29 10:03:58 -04:00
|
|
|
|
2019-09-19 13:16:17 -04:00
|
|
|
ruby_version_is "2.7" do
|
|
|
|
it "permits self as a receiver" do
|
|
|
|
receiver = LangSendSpecs::PrivateGetter.new
|
2019-09-29 10:03:58 -04:00
|
|
|
receiver.call_self_foo_or_equals(6)
|
|
|
|
receiver.call_self_foo.should == 6
|
2019-09-19 13:16:17 -04:00
|
|
|
end
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "Invoking a method" do
|
|
|
|
describe "with required args after the rest arguments" do
|
|
|
|
it "binds the required arguments first" do
|
|
|
|
specs.fooM0RQ1(1).should == [[], 1]
|
|
|
|
specs.fooM0RQ1(1,2).should == [[1], 2]
|
|
|
|
specs.fooM0RQ1(1,2,3).should == [[1,2], 3]
|
|
|
|
|
|
|
|
specs.fooM1RQ1(1,2).should == [1, [], 2]
|
|
|
|
specs.fooM1RQ1(1,2,3).should == [1, [2], 3]
|
|
|
|
specs.fooM1RQ1(1,2,3,4).should == [1, [2, 3], 4]
|
|
|
|
|
|
|
|
specs.fooM1O1RQ1(1,2).should == [1, 9, [], 2]
|
|
|
|
specs.fooM1O1RQ1(1,2,3).should == [1, 2, [], 3]
|
|
|
|
specs.fooM1O1RQ1(1,2,3,4).should == [1, 2, [3], 4]
|
|
|
|
|
|
|
|
specs.fooM1O1RQ2(1,2,3).should == [1, 9, [], 2, 3]
|
|
|
|
specs.fooM1O1RQ2(1,2,3,4).should == [1, 2, [], 3, 4]
|
|
|
|
specs.fooM1O1RQ2(1,2,3,4,5).should == [1, 2, [3], 4, 5]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "with mandatory arguments after optional arguments" do
|
|
|
|
it "binds the required arguments first" do
|
|
|
|
specs.fooO1Q1(0,1).should == [0,1]
|
|
|
|
specs.fooO1Q1(2).should == [1,2]
|
|
|
|
|
|
|
|
specs.fooM1O1Q1(2,3,4).should == [2,3,4]
|
|
|
|
specs.fooM1O1Q1(1,3).should == [1,2,3]
|
|
|
|
|
|
|
|
specs.fooM2O1Q1(1,2,4).should == [1,2,3,4]
|
|
|
|
|
|
|
|
specs.fooM2O2Q1(1,2,3,4,5).should == [1,2,3,4,5]
|
|
|
|
specs.fooM2O2Q1(1,2,3,5).should == [1,2,3,4,5]
|
|
|
|
specs.fooM2O2Q1(1,2,5).should == [1,2,3,4,5]
|
|
|
|
|
|
|
|
specs.fooO4Q1(1,2,3,4,5).should == [1,2,3,4,5]
|
|
|
|
specs.fooO4Q1(1,2,3,5).should == [1,2,3,4,5]
|
|
|
|
specs.fooO4Q1(1,2,5).should == [1,2,3,4,5]
|
|
|
|
specs.fooO4Q1(1,5).should == [1,2,3,4,5]
|
|
|
|
specs.fooO4Q1(5).should == [1,2,3,4,5]
|
|
|
|
|
|
|
|
specs.fooO4Q2(1,2,3,4,5,6).should == [1,2,3,4,5,6]
|
|
|
|
specs.fooO4Q2(1,2,3,5,6).should == [1,2,3,4,5,6]
|
|
|
|
specs.fooO4Q2(1,2,5,6).should == [1,2,3,4,5,6]
|
|
|
|
specs.fooO4Q2(1,5,6).should == [1,2,3,4,5,6]
|
|
|
|
specs.fooO4Q2(5,6).should == [1,2,3,4,5,6]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with .() invokes #call" do
|
|
|
|
q = proc { |z| z }
|
|
|
|
q.(1).should == 1
|
|
|
|
|
|
|
|
obj = mock("paren call")
|
|
|
|
obj.should_receive(:call).and_return(:called)
|
|
|
|
obj.().should == :called
|
|
|
|
end
|
|
|
|
|
|
|
|
it "allows a vestigial trailing ',' in the arguments" do
|
|
|
|
specs.fooM1(1,).should == [1]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with splat operator attempts to coerce it to an Array if the object respond_to?(:to_a)" do
|
|
|
|
ary = [2,3,4]
|
|
|
|
obj = mock("to_a")
|
|
|
|
obj.should_receive(:to_a).and_return(ary).twice
|
|
|
|
specs.fooM0R(*obj).should == ary
|
|
|
|
specs.fooM1R(1,*obj).should == [1, ary]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with splat operator * and non-Array value uses value unchanged if it does not respond_to?(:to_ary)" do
|
|
|
|
obj = Object.new
|
|
|
|
obj.should_not respond_to(:to_a)
|
|
|
|
|
|
|
|
specs.fooM0R(*obj).should == [obj]
|
|
|
|
specs.fooM1R(1,*obj).should == [1, [obj]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts additional arguments after splat expansion" do
|
|
|
|
a = [1,2]
|
|
|
|
specs.fooM4(*a,3,4).should == [1,2,3,4]
|
|
|
|
specs.fooM4(0,*a,3).should == [0,1,2,3]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not expand final array arguments after a splat expansion" do
|
|
|
|
a = [1, 2]
|
|
|
|
specs.fooM3(*a, [3, 4]).should == [1, 2, [3, 4]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts final explicit literal Hash arguments after the splat" do
|
|
|
|
a = [1, 2]
|
|
|
|
specs.fooM0RQ1(*a, { a: 1 }).should == [[1, 2], { a: 1 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts final implicit literal Hash arguments after the splat" do
|
|
|
|
a = [1, 2]
|
|
|
|
specs.fooM0RQ1(*a, a: 1).should == [[1, 2], { a: 1 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts final Hash arguments after the splat" do
|
|
|
|
a = [1, 2]
|
|
|
|
b = { a: 1 }
|
|
|
|
specs.fooM0RQ1(*a, b).should == [[1, 2], { a: 1 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts mandatory and explicit literal Hash arguments after the splat" do
|
|
|
|
a = [1, 2]
|
|
|
|
specs.fooM0RQ2(*a, 3, { a: 1 }).should == [[1, 2], 3, { a: 1 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts mandatory and implicit literal Hash arguments after the splat" do
|
|
|
|
a = [1, 2]
|
|
|
|
specs.fooM0RQ2(*a, 3, a: 1).should == [[1, 2], 3, { a: 1 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts mandatory and Hash arguments after the splat" do
|
|
|
|
a = [1, 2]
|
|
|
|
b = { a: 1 }
|
|
|
|
specs.fooM0RQ2(*a, 3, b).should == [[1, 2], 3, { a: 1 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "converts a final splatted explicit Hash to an Array" do
|
|
|
|
a = [1, 2]
|
|
|
|
specs.fooR(*a, 3, *{ a: 1 }).should == [1, 2, 3, [:a, 1]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls #to_a to convert a final splatted Hash object to an Array" do
|
|
|
|
a = [1, 2]
|
|
|
|
b = { a: 1 }
|
|
|
|
b.should_receive(:to_a).and_return([:a, 1])
|
|
|
|
|
|
|
|
specs.fooR(*a, 3, *b).should == [1, 2, 3, :a, 1]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts multiple splat expansions in the same argument list" do
|
|
|
|
a = [1,2,3]
|
|
|
|
b = 7
|
|
|
|
c = mock("pseudo-array")
|
|
|
|
c.should_receive(:to_a).and_return([0,0])
|
|
|
|
|
|
|
|
d = [4,5]
|
|
|
|
specs.rest_len(*a,*d,6,*b).should == 7
|
|
|
|
specs.rest_len(*a,*a,*a).should == 9
|
|
|
|
specs.rest_len(0,*a,4,*5,6,7,*c,-1).should == 11
|
|
|
|
end
|
|
|
|
|
2020-09-15 15:54:31 -04:00
|
|
|
ruby_version_is ""..."3.0" do
|
Dup splat array in certain cases where there is a block argument
This makes:
```ruby
args = [1, 2, -> {}]; foo(*args, &args.pop)
```
call `foo` with 1, 2, and the lambda, in addition to passing the
lambda as a block. This is different from the previous behavior,
which passed the lambda as a block but not as a regular argument,
which goes against the expected left-to-right evaluation order.
This is how Ruby already compiled arguments if using leading
arguments, trailing arguments, or keywords in the same call.
This works by disabling the optimization that skipped duplicating
the array during the splat (splatarray instruction argument
switches from false to true). In the above example, the splat
call duplicates the array. I've tested and cases where a
local variable or symbol are used do not duplicate the array,
so I don't expect this to decrease the performance of most Ruby
programs. However, programs such as:
```ruby
foo(*args, &bar)
```
could see a decrease in performance, if `bar` is a method call
and not a local variable.
This is not a perfect solution, there are ways to get around
this:
```ruby
args = Struct.new(:a).new([:x, :y])
def args.to_a; a; end
def args.to_proc; a.pop; ->{}; end
foo(*args, &args)
# calls foo with 1 argument (:x)
# not 2 arguments (:x and :y)
```
A perfect solution would require completely disabling the
optimization.
Fixes [Bug #16504]
Fixes [Bug #16500]
2020-05-28 17:59:11 -04:00
|
|
|
it "expands the Array elements from the splat after executing the arguments and block if no other arguments follow the splat" do
|
|
|
|
def self.m(*args, &block)
|
|
|
|
[args, block]
|
|
|
|
end
|
|
|
|
|
|
|
|
args = [1, nil]
|
|
|
|
m(*args, &args.pop).should == [[1], nil]
|
|
|
|
|
|
|
|
args = [1, nil]
|
|
|
|
order = []
|
|
|
|
m(*(order << :args; args), &(order << :block; args.pop)).should == [[1], nil]
|
|
|
|
order.should == [:args, :block]
|
2017-12-27 11:12:47 -05:00
|
|
|
end
|
Dup splat array in certain cases where there is a block argument
This makes:
```ruby
args = [1, 2, -> {}]; foo(*args, &args.pop)
```
call `foo` with 1, 2, and the lambda, in addition to passing the
lambda as a block. This is different from the previous behavior,
which passed the lambda as a block but not as a regular argument,
which goes against the expected left-to-right evaluation order.
This is how Ruby already compiled arguments if using leading
arguments, trailing arguments, or keywords in the same call.
This works by disabling the optimization that skipped duplicating
the array during the splat (splatarray instruction argument
switches from false to true). In the above example, the splat
call duplicates the array. I've tested and cases where a
local variable or symbol are used do not duplicate the array,
so I don't expect this to decrease the performance of most Ruby
programs. However, programs such as:
```ruby
foo(*args, &bar)
```
could see a decrease in performance, if `bar` is a method call
and not a local variable.
This is not a perfect solution, there are ways to get around
this:
```ruby
args = Struct.new(:a).new([:x, :y])
def args.to_a; a; end
def args.to_proc; a.pop; ->{}; end
foo(*args, &args)
# calls foo with 1 argument (:x)
# not 2 arguments (:x and :y)
```
A perfect solution would require completely disabling the
optimization.
Fixes [Bug #16504]
Fixes [Bug #16500]
2020-05-28 17:59:11 -04:00
|
|
|
end
|
2017-12-27 11:12:47 -05:00
|
|
|
|
2020-09-15 15:54:31 -04:00
|
|
|
ruby_version_is "3.0" do
|
Dup splat array in certain cases where there is a block argument
This makes:
```ruby
args = [1, 2, -> {}]; foo(*args, &args.pop)
```
call `foo` with 1, 2, and the lambda, in addition to passing the
lambda as a block. This is different from the previous behavior,
which passed the lambda as a block but not as a regular argument,
which goes against the expected left-to-right evaluation order.
This is how Ruby already compiled arguments if using leading
arguments, trailing arguments, or keywords in the same call.
This works by disabling the optimization that skipped duplicating
the array during the splat (splatarray instruction argument
switches from false to true). In the above example, the splat
call duplicates the array. I've tested and cases where a
local variable or symbol are used do not duplicate the array,
so I don't expect this to decrease the performance of most Ruby
programs. However, programs such as:
```ruby
foo(*args, &bar)
```
could see a decrease in performance, if `bar` is a method call
and not a local variable.
This is not a perfect solution, there are ways to get around
this:
```ruby
args = Struct.new(:a).new([:x, :y])
def args.to_a; a; end
def args.to_proc; a.pop; ->{}; end
foo(*args, &args)
# calls foo with 1 argument (:x)
# not 2 arguments (:x and :y)
```
A perfect solution would require completely disabling the
optimization.
Fixes [Bug #16504]
Fixes [Bug #16500]
2020-05-28 17:59:11 -04:00
|
|
|
it "expands the Array elements from the splat before applying block argument operations" do
|
|
|
|
def self.m(*args, &block)
|
|
|
|
[args, block]
|
|
|
|
end
|
2017-12-27 11:12:47 -05:00
|
|
|
|
Dup splat array in certain cases where there is a block argument
This makes:
```ruby
args = [1, 2, -> {}]; foo(*args, &args.pop)
```
call `foo` with 1, 2, and the lambda, in addition to passing the
lambda as a block. This is different from the previous behavior,
which passed the lambda as a block but not as a regular argument,
which goes against the expected left-to-right evaluation order.
This is how Ruby already compiled arguments if using leading
arguments, trailing arguments, or keywords in the same call.
This works by disabling the optimization that skipped duplicating
the array during the splat (splatarray instruction argument
switches from false to true). In the above example, the splat
call duplicates the array. I've tested and cases where a
local variable or symbol are used do not duplicate the array,
so I don't expect this to decrease the performance of most Ruby
programs. However, programs such as:
```ruby
foo(*args, &bar)
```
could see a decrease in performance, if `bar` is a method call
and not a local variable.
This is not a perfect solution, there are ways to get around
this:
```ruby
args = Struct.new(:a).new([:x, :y])
def args.to_a; a; end
def args.to_proc; a.pop; ->{}; end
foo(*args, &args)
# calls foo with 1 argument (:x)
# not 2 arguments (:x and :y)
```
A perfect solution would require completely disabling the
optimization.
Fixes [Bug #16504]
Fixes [Bug #16500]
2020-05-28 17:59:11 -04:00
|
|
|
args = [1, nil]
|
|
|
|
m(*args, &args.pop).should == [[1, nil], nil]
|
|
|
|
|
|
|
|
args = [1, nil]
|
|
|
|
order = []
|
|
|
|
m(*(order << :args; args), &(order << :block; args.pop)).should == [[1, nil], nil]
|
|
|
|
order.should == [:args, :block]
|
|
|
|
end
|
2017-12-27 11:12:47 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it "evaluates the splatted arguments before the block if there are other arguments after the splat" do
|
|
|
|
def self.m(*args, &block)
|
|
|
|
[args, block]
|
|
|
|
end
|
|
|
|
|
|
|
|
args = [1, nil]
|
|
|
|
m(*args, 2, &args.pop).should == [[1, nil, 2], nil]
|
|
|
|
end
|
|
|
|
|
2017-05-07 08:04:49 -04:00
|
|
|
it "expands an array to arguments grouped in parentheses" do
|
|
|
|
specs.destructure2([40,2]).should == 42
|
|
|
|
end
|
|
|
|
|
|
|
|
it "expands an array to arguments grouped in parentheses and ignores any rest arguments in the array" do
|
|
|
|
specs.destructure2([40,2,84]).should == 42
|
|
|
|
end
|
|
|
|
|
|
|
|
it "expands an array to arguments grouped in parentheses and sets not specified arguments to nil" do
|
|
|
|
specs.destructure2b([42]).should == [42, nil]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "expands an array to arguments grouped in parentheses which in turn takes rest arguments" do
|
|
|
|
specs.destructure4r([1, 2, 3]).should == [1, 2, [], 3, nil]
|
|
|
|
specs.destructure4r([1, 2, 3, 4]).should == [1, 2, [], 3, 4]
|
|
|
|
specs.destructure4r([1, 2, 3, 4, 5]).should == [1, 2, [3], 4, 5]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with optional argument(s), expands an array to arguments grouped in parentheses" do
|
|
|
|
specs.destructure4o(1, [2, 3]).should == [1, 1, nil, [2, 3]]
|
|
|
|
specs.destructure4o(1, [], 2).should == [1, nil, nil, 2]
|
|
|
|
specs.destructure4os(1, [2, 3]).should == [1, 2, [3]]
|
|
|
|
specs.destructure5o(1, [2, 3]).should == [1, 2, 1, nil, [2, 3]]
|
|
|
|
specs.destructure7o(1, [2, 3]).should == [1, 2, 1, nil, 2, 3]
|
|
|
|
specs.destructure7b(1, [2, 3]) do |(a,*b,c)|
|
|
|
|
[a, c]
|
|
|
|
end.should == [1, 3]
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "new-style hash arguments" do
|
|
|
|
describe "as the only parameter" do
|
|
|
|
it "passes without curly braces" do
|
|
|
|
specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
|
|
|
|
[{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "passes without curly braces or parens" do
|
|
|
|
(specs.fooM1 rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
|
|
|
|
[{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "handles a hanging comma without curly braces" do
|
|
|
|
specs.fooM1(abc: 123,).should == [{abc: 123}]
|
|
|
|
specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
|
|
|
|
[{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "as the last parameter" do
|
|
|
|
it "passes without curly braces" do
|
|
|
|
specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
|
|
|
|
['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "passes without curly braces or parens" do
|
|
|
|
(specs.fooM3 'abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
|
|
|
|
['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "handles a hanging comma without curly braces" do
|
|
|
|
specs.fooM3('abc', 123, abc: 123,).should == ['abc', 123, {abc: 123}]
|
|
|
|
specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
|
|
|
|
['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "mixed new- and old-style hash arguments" do
|
|
|
|
describe "as the only parameter" do
|
|
|
|
it "passes without curly braces" do
|
|
|
|
specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
|
|
|
|
[{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "passes without curly braces or parens" do
|
|
|
|
(specs.fooM1 rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
|
|
|
|
[{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "handles a hanging comma without curly braces" do
|
|
|
|
specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
|
|
|
|
[{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "as the last parameter" do
|
|
|
|
it "passes without curly braces" do
|
|
|
|
specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
|
|
|
|
['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "passes without curly braces or parens" do
|
|
|
|
(specs.fooM3 'abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should ==
|
|
|
|
['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "handles a hanging comma without curly braces" do
|
|
|
|
specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should ==
|
|
|
|
['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "allows []= with arguments after splat" do
|
|
|
|
before :each do
|
|
|
|
@obj = LangSendSpecs::Attr19Set.new
|
|
|
|
@ary = ["a"]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "with *args in the [] and post args" do
|
|
|
|
@obj[1,*@ary,123] = 2
|
|
|
|
@obj.result.should == [1, "a", 123, 2]
|
|
|
|
end
|
|
|
|
end
|