diff options
Diffstat (limited to 'spec/ruby/language/method_spec.rb')
| -rw-r--r-- | spec/ruby/language/method_spec.rb | 1649 |
1 files changed, 1649 insertions, 0 deletions
diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb new file mode 100644 index 0000000000..8f72bd45ed --- /dev/null +++ b/spec/ruby/language/method_spec.rb @@ -0,0 +1,1649 @@ +require_relative '../spec_helper' + +describe "A method send" do + evaluate <<-ruby do + def m(a) a end + ruby + + a = b = m 1 + a.should == 1 + b.should == 1 + end + + context "with a single splatted Object argument" do + before :all do + def m(a) a end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + m(*x).should equal(x) + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1]) + + m(*x).should == 1 + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + m(*x).should == x + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { m(*x) }.should raise_error(TypeError) + end + end + + context "with a leading splatted Object argument" do + before :all do + def m(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + m(*x, 1, 2, 3).should == [x, 1, [], 2, 3] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1]) + + m(*x, 2, 3, 4).should == [1, 2, [], 3, 4] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + m(*x, 2, 3, 4).should == [x, 2, [], 3, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { m(*x, 2, 3) }.should raise_error(TypeError) + end + end + + context "with a middle splatted Object argument" do + before :all do + def m(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + m(1, 2, *x, 3, 4).should == [1, 2, [x], 3, 4] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([5, 6, 7]) + + m(1, 2, *x, 3).should == [1, 2, [5, 6], 7, 3] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + m(1, 2, *x, 4).should == [1, 2, [], x, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { m(1, *x, 2, 3) }.should raise_error(TypeError) + end + + it "copies the splatted array" do + args = [3, 4] + m(1, 2, *args, 4, 5).should == [1, 2, [3, 4], 4, 5] + m(1, 2, *args, 4, 5)[2].should_not equal(args) + end + + it "allows an array being splatted to be modified by another argument" do + args = [3, 4] + m(1, args.shift, *args, 4, 5).should == [1, 3, [4], 4, 5] + end + end + + context "with a trailing splatted Object argument" do + before :all do + def m(a, *b, c) [a, b, c] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + m(1, 2, *x).should == [1, [2], x] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([5, 6, 7]) + + m(1, 2, *x).should == [1, [2, 5, 6], 7] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + m(1, 2, *x, 4).should == [1, [2, x], 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { m(1, 2, *x) }.should raise_error(TypeError) + end + end + + context "with a block argument" do + before :all do + def m(x) + if block_given? + [true, yield(x + 'b')] + else + [false] + end + end + end + + it "that refers to a proc passes the proc as the block" do + m('a', &-> y { y + 'c'}).should == [true, 'abc'] + end + + it "that is nil passes no block" do + m('a', &nil).should == [false] + end + end +end + +describe "An element assignment method send" do + before :each do + ScratchPad.clear + end + + context "with a single splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.[]=(a, b) ScratchPad.record [a, b] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o[*x] = 1).should == 1 + ScratchPad.recorded.should == [x, 1] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1]) + + (@o[*x] = 2).should == 2 + ScratchPad.recorded.should == [1, 2] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o[*x] = 1).should == 1 + ScratchPad.recorded.should == [x, 1] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { @o[*x] = 1 }.should raise_error(TypeError) + end + end + + context "with a leading splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o[*x, 2, 3, 4] = 1).should == 1 + ScratchPad.recorded.should == [x, 2, [3], 4, 1] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1, 2, 3]) + + (@o[*x, 4, 5] = 6).should == 6 + ScratchPad.recorded.should == [1, 2, [3, 4], 5, 6] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o[*x, 2, 3, 4] = 5).should == 5 + ScratchPad.recorded.should == [x, 2, [3], 4, 5] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { @o[*x, 2, 3] = 4 }.should raise_error(TypeError) + end + end + + context "with a middle splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o[1, *x, 2, 3] = 4).should == 4 + ScratchPad.recorded.should == [1, x, [2], 3, 4] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([2, 3]) + + (@o[1, *x, 4] = 5).should == 5 + ScratchPad.recorded.should == [1, 2, [3], 4, 5] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o[1, 2, *x, 3] = 4).should == 4 + ScratchPad.recorded.should == [1, 2, [x], 3, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { @o[1, 2, *x, 3] = 4 }.should raise_error(TypeError) + end + end + + context "with a trailing splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o[1, 2, 3, 4, *x] = 5).should == 5 + ScratchPad.recorded.should == [1, 2, [3, 4], x, 5] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([4, 5]) + + (@o[1, 2, 3, *x] = 6).should == 6 + ScratchPad.recorded.should == [1, 2, [3, 4], 5, 6] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o[1, 2, 3, *x] = 4).should == 4 + ScratchPad.recorded.should == [1, 2, [3], x, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { @o[1, 2, 3, *x] = 4 }.should raise_error(TypeError) + end + end +end + +describe "An attribute assignment method send" do + context "with a single splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.m=(a, b) [a, b] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o.send :m=, *x, 1).should == [x, 1] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1]) + + (@o.send :m=, *x, 2).should == [1, 2] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o.send :m=, *x, 1).should == [x, 1] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { @o.send :m=, *x, 1 }.should raise_error(TypeError) + end + end + + context "with a leading splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o.send :m=, *x, 2, 3, 4, 1).should == [x, 2, [3], 4, 1] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1, 2, 3]) + + (@o.send :m=, *x, 4, 5, 6).should == [1, 2, [3, 4], 5, 6] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o.send :m=, *x, 2, 3, 4, 5).should == [x, 2, [3], 4, 5] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { @o.send :m=, *x, 2, 3, 4 }.should raise_error(TypeError) + end + end + + context "with a middle splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o.send :m=, 1, *x, 2, 3, 4).should == [1, x, [2], 3, 4] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([2, 3]) + + (@o.send :m=, 1, *x, 4, 5).should == [1, 2, [3], 4, 5] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o.send :m=, 1, 2, *x, 3, 4).should == [1, 2, [x], 3, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { @o.send :m=, 1, 2, *x, 3, 4 }.should raise_error(TypeError) + end + end + + context "with a trailing splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o.send :m=, 1, 2, 3, 4, *x, 5).should == [1, 2, [3, 4], x, 5] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([4, 5]) + + (@o.send :m=, 1, 2, 3, *x, 6).should == [1, 2, [3, 4], 5, 6] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o.send :m=, 1, 2, 3, *x, 4).should == [1, 2, [3], x, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + -> { @o.send :m=, 1, 2, 3, *x, 4 }.should raise_error(TypeError) + end + end +end + +describe "A method" do + SpecEvaluate.desc = "for definition" + + context "assigns no local variables" do + evaluate <<-ruby do + def m + end + ruby + + m.should be_nil + end + + evaluate <<-ruby do + def m() + end + ruby + + m.should be_nil + end + end + + context "assigns local variables from method parameters" do + evaluate <<-ruby do + def m(a) a end + ruby + + m((args = 1, 2, 3)).should equal(args) + end + + evaluate <<-ruby do + def m((a)) a end + ruby + + m(1).should == 1 + m([1, 2, 3]).should == 1 + end + + evaluate <<-ruby do + def m((*a, b)) [a, b] end + ruby + + m(1).should == [[], 1] + m([1, 2, 3]).should == [[1, 2], 3] + end + + evaluate <<-ruby do + def m(a=1) a end + ruby + + m().should == 1 + m(2).should == 2 + end + + evaluate <<-ruby do + def m() end + ruby + + m().should be_nil + m(*[]).should be_nil + m(**{}).should be_nil + end + + evaluate <<-ruby do + def m(*) end + ruby + + m().should be_nil + m(1).should be_nil + m(1, 2, 3).should be_nil + end + + evaluate <<-ruby do + def m(*a) a end + ruby + + m().should == [] + m(1).should == [1] + m(1, 2, 3).should == [1, 2, 3] + m(*[]).should == [] + m(**{}).should == [] + end + + evaluate <<-ruby do + def m(a:) a end + ruby + + -> { m() }.should raise_error(ArgumentError) + m(a: 1).should == 1 + suppress_keyword_warning do + -> { m("a" => 1, a: 1) }.should raise_error(ArgumentError) + end + end + + evaluate <<-ruby do + def m(a:, **kw) [a, kw] end + ruby + + -> { m(b: 1) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a: 1) a end + ruby + + m().should == 1 + m(a: 2).should == 2 + end + + evaluate <<-ruby do + def m(**) end + ruby + + m().should be_nil + m(a: 1, b: 2).should be_nil + -> { m(1) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(**k) k end + ruby + + m().should == {} + m(a: 1, b: 2).should == { a: 1, b: 2 } + m(*[]).should == {} + m(**{}).should == {} + suppress_warning { + eval "m(**{a: 1, b: 2}, **{a: 4, c: 7})" + }.should == { a: 4, b: 2, c: 7 } + -> { m(2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(**k); k end; + ruby + + m("a" => 1).should == { "a" => 1 } + end + + evaluate <<-ruby do + def m(&b) b end + ruby + + m { }.should be_an_instance_of(Proc) + end + + evaluate <<-ruby do + def m(a, b) [a, b] end + ruby + + m(1, 2).should == [1, 2] + end + + evaluate <<-ruby do + def m(a, (b, c)) [a, b, c] end + ruby + + m(1, 2).should == [1, 2, nil] + m(1, [2, 3, 4]).should == [1, 2, 3] + end + + evaluate <<-ruby do + def m((a), (b)) [a, b] end + ruby + + m(1, 2).should == [1, 2] + m([1, 2], [3, 4]).should == [1, 3] + end + + evaluate <<-ruby do + def m((*), (*)) end + ruby + + m(2, 3).should be_nil + m([2, 3, 4], [5, 6]).should be_nil + -> { m a: 1 }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m((*a), (*b)) [a, b] end + ruby + + m(1, 2).should == [[1], [2]] + m([1, 2], [3, 4]).should == [[1, 2], [3, 4]] + end + + evaluate <<-ruby do + def m((a, b), (c, d)) + [a, b, c, d] + end + ruby + + m(1, 2).should == [1, nil, 2, nil] + m([1, 2, 3], [4, 5, 6]).should == [1, 2, 4, 5] + end + + evaluate <<-ruby do + def m((a, *b), (*c, d)) + [a, b, c, d] + end + ruby + + m(1, 2).should == [1, [], [], 2] + m([1, 2, 3], [4, 5, 6]).should == [1, [2, 3], [4, 5], 6] + end + + evaluate <<-ruby do + def m((a, b, *c, d), (*e, f, g), (*h)) + [a, b, c, d, e, f, g, h] + end + ruby + + m(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]] + result = m([1, 2, 3], [4, 5, 6, 7, 8], [9, 10]) + result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]] + end + + evaluate <<-ruby do + def m(a, (b, (c, *d), *e)) + [a, b, c, d, e] + end + ruby + + m(1, 2).should == [1, 2, nil, [], []] + m(1, [2, [3, 4, 5], 6, 7, 8]).should == [1, 2, 3, [4, 5], [6, 7, 8]] + end + + evaluate <<-ruby do + def m(a, (b, (c, *d, (e, (*f)), g), (h, (i, j)))) + [a, b, c, d, e, f, g, h, i, j] + end + ruby + + m(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil] + result = m(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]]) + result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12] + end + + evaluate <<-ruby do + def m(a, b=1) [a, b] end + ruby + + m(2).should == [2, 1] + m(1, 2).should == [1, 2] + end + + evaluate <<-ruby do + def m(a, *) a end + ruby + + m(1).should == 1 + m(1, 2, 3).should == 1 + end + + evaluate <<-ruby do + def m(a, *b) [a, b] end + ruby + + m(1).should == [1, []] + m(1, 2, 3).should == [1, [2, 3]] + end + + evaluate <<-ruby do + def m(a, b:) [a, b] end + ruby + + m(1, b: 2).should == [1, 2] + suppress_keyword_warning do + -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) + end + end + + evaluate <<-ruby do + def m(a, b: 1) [a, b] end + ruby + + m(2).should == [2, 1] + m(1, b: 2).should == [1, 2] + -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a, **) a end + ruby + + m(1).should == 1 + m(1, a: 2, b: 3).should == 1 + -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a, **k) [a, k] end + ruby + + m(1).should == [1, {}] + m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}] + -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a, &b) [a, b] end + ruby + + m(1).should == [1, nil] + m(1, &(l = -> {})).should == [1, l] + end + + evaluate <<-ruby do + def m(a=1, b) [a, b] end + ruby + + m(2).should == [1, 2] + m(2, 3).should == [2, 3] + end + + evaluate <<-ruby do + def m(a=1, *) a end + ruby + + m().should == 1 + m(2, 3, 4).should == 2 + end + + evaluate <<-ruby do + def m(a=1, *b) [a, b] end + ruby + + m().should == [1, []] + m(2, 3, 4).should == [2, [3, 4]] + end + + evaluate <<-ruby do + def m(a=1, (b, c)) [a, b, c] end + ruby + + m(2).should == [1, 2, nil] + m(2, 3).should == [2, 3, nil] + m(2, [3, 4, 5]).should == [2, 3, 4] + end + + evaluate <<-ruby do + def m(a=1, (b, (c, *d))) [a, b, c, d] end + ruby + + m(2).should == [1, 2, nil, []] + m(2, 3).should == [2, 3, nil, []] + m(2, [3, [4, 5, 6], 7]).should == [2, 3, 4, [5, 6]] + end + + evaluate <<-ruby do + def m(a=1, (b, (c, *d), *e)) [a, b, c, d, e] end + ruby + + m(2).should == [1, 2, nil, [], []] + m(2, [3, 4, 5, 6]).should == [2, 3, 4, [], [5, 6]] + m(2, [3, [4, 5, 6], 7]).should == [2, 3, 4, [5, 6], [7]] + end + + evaluate <<-ruby do + def m(a=1, (b), (c)) [a, b, c] end + ruby + + m(2, 3).should == [1, 2, 3] + m(2, 3, 4).should == [2, 3, 4] + m(2, [3, 4], [5, 6, 7]).should == [2, 3, 5] + end + + evaluate <<-ruby do + def m(a=1, (*b), (*c)) [a, b, c] end + ruby + + -> { m() }.should raise_error(ArgumentError) + -> { m(2) }.should raise_error(ArgumentError) + m(2, 3).should == [1, [2], [3]] + m(2, [3, 4], [5, 6]).should == [2, [3, 4], [5, 6]] + end + + evaluate <<-ruby do + def m(a=1, (b, c), (d, e)) [a, b, c, d, e] end + ruby + + m(2, 3).should == [1, 2, nil, 3, nil] + m(2, [3, 4, 5], [6, 7, 8]).should == [2, 3, 4, 6, 7] + end + + evaluate <<-ruby do + def m(a=1, (b, *c), (*d, e)) + [a, b, c, d, e] + end + ruby + + m(1, 2).should == [1, 1, [], [], 2] + m(1, [2, 3], [4, 5, 6]).should == [1, 2, [3], [4, 5], 6] + end + + evaluate <<-ruby do + def m(a=1, (b, *c), (d, (*e, f))) + [a, b, c, d, e, f] + end + ruby + + m(1, 2).should == [1, 1, [], 2, [], nil] + m(nil, nil).should == [1, nil, [], nil, [], nil] + result = m([1, 2, 3], [4, 5, 6], [7, 8, 9]) + result.should == [[1, 2, 3], 4, [5, 6], 7, [], 8] + end + + evaluate <<-ruby do + def m(a=1, b:) [a, b] end + ruby + + m(b: 2).should == [1, 2] + m(2, b: 1).should == [2, 1] + -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a=1, b: 2) [a, b] end + ruby + + m().should == [1, 2] + m(2).should == [2, 2] + m(b: 3).should == [1, 3] + -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a=1, **) a end + ruby + + m().should == 1 + m(2, a: 1, b: 0).should == 2 + m("a" => 1, a: 2).should == 1 + end + + evaluate <<-ruby do + def m(a=1, **k) [a, k] end + ruby + + m().should == [1, {}] + m(2, a: 1, b: 2).should == [2, {a: 1, b: 2}] + end + + evaluate <<-ruby do + def m(a=1, &b) [a, b] end + ruby + + m().should == [1, nil] + m(&(l = -> {})).should == [1, l] + + p = -> {} + l = mock("to_proc") + l.should_receive(:to_proc).and_return(p) + m(&l).should == [1, p] + end + + evaluate <<-ruby do + def m(*, a) a end + ruby + + m(1).should == 1 + m(1, 2, 3).should == 3 + end + + evaluate <<-ruby do + def m(*a, b) [a, b] end + ruby + + m(1).should == [[], 1] + m(1, 2, 3).should == [[1, 2], 3] + end + + evaluate <<-ruby do + def m(*, &b) b end + ruby + + m().should be_nil + m(1, 2, 3, 4).should be_nil + m(&(l = ->{})).should equal(l) + end + + evaluate <<-ruby do + def m(*a, &b) [a, b] end + ruby + + m().should == [[], nil] + m(1).should == [[1], nil] + m(1, 2, 3, &(l = -> {})).should == [[1, 2, 3], l] + end + + evaluate <<-ruby do + def m(a:, b:) [a, b] end + ruby + + m(a: 1, b: 2).should == [1, 2] + suppress_keyword_warning do + -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) + end + end + + evaluate <<-ruby do + def m(a:, b: 1) [a, b] end + ruby + + m(a: 1).should == [1, 1] + m(a: 1, b: 2).should == [1, 2] + suppress_keyword_warning do + -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) + end + end + + evaluate <<-ruby do + def m(a:, **) a end + ruby + + m(a: 1).should == 1 + m(a: 1, b: 2).should == 1 + m("a" => 1, a: 1, b: 2).should == 1 + end + + evaluate <<-ruby do + def m(a:, **k) [a, k] end + ruby + + m(a: 1).should == [1, {}] + m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}] + m("a" => 1, a: 1, b: 2).should == [1, {"a" => 1, b: 2}] + end + + evaluate <<-ruby do + def m(a:, &b) [a, b] end + ruby + + m(a: 1).should == [1, nil] + m(a: 1, &(l = ->{})).should == [1, l] + end + + evaluate <<-ruby do + def m(a: 1, b:) [a, b] end + ruby + + m(b: 0).should == [1, 0] + m(b: 2, a: 3).should == [3, 2] + end + + evaluate <<-ruby do + def m(a: def m(a: 1) a end, b:) + [a, b] + end + ruby + + m(a: 2, b: 3).should == [2, 3] + m(b: 1).should == [:m, 1] + + # Note the default value of a: in the original method. + m().should == 1 + end + + evaluate <<-ruby do + def m(a: 1, b: 2) [a, b] end + ruby + + m().should == [1, 2] + m(b: 3, a: 4).should == [4, 3] + end + + evaluate <<-ruby do + def m(a: 1, **) a end + ruby + + m().should == 1 + m(a: 2, b: 1).should == 2 + end + + evaluate <<-ruby do + def m(a: 1, **k) [a, k] end + ruby + + m(b: 2, c: 3).should == [1, {b: 2, c: 3}] + end + + evaluate <<-ruby do + def m(a: 1, &b) [a, b] end + ruby + + m(&(l = ->{})).should == [1, l] + m().should == [1, nil] + end + + evaluate <<-ruby do + def m(**, &b) b end + ruby + + m(a: 1, b: 2, &(l = ->{})).should == l + end + + evaluate <<-ruby do + def m(**k, &b) [k, b] end + ruby + + m(a: 1, b: 2).should == [{ a: 1, b: 2}, nil] + end + + evaluate <<-ruby do + def m(a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l) + [a, b, c, d, e, f, g, h, k, l] + end + ruby + + result = m(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{})) + result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l] + end + + evaluate <<-ruby do + def m(a, b=1, *c, d, e:, f: 2, g:, **k, &l) + [a, b, c, d, e, f, g, k, l] + end + ruby + + result = m(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{})) + result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] + end + + evaluate <<-ruby do + def m(a, **nil); a end; + ruby + + m({a: 1}).should == {a: 1} + m({"a" => 1}).should == {"a" => 1} + + -> { m(a: 1) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { m(**{a: 1}) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { m("a" => 1) }.should raise_error(ArgumentError, 'no keywords accepted') + end + + evaluate <<-ruby do + def m(a, b = nil, c = nil, d, e: nil, **f) + [a, b, c, d, e, f] + end + ruby + + result = m(1, 2) + result.should == [1, nil, nil, 2, nil, {}] + + result = m(1, 2, {foo: :bar}) + result.should == [1, 2, nil, {foo: :bar}, nil, {}] + + result = m(1, {foo: :bar}) + result.should == [1, nil, nil, {foo: :bar}, nil, {}] + end + end + + context 'when passing an empty keyword splat to a method that does not accept keywords' do + evaluate <<-ruby do + def m(*a); a; end + ruby + + h = {} + m(**h).should == [] + end + end + + context 'when passing an empty keyword splat to a method that does not accept keywords' do + evaluate <<-ruby do + def m(a); a; end + ruby + h = {} + + -> do + m(**h).should == {} + end.should raise_error(ArgumentError) + end + end + + context "raises ArgumentError if passing hash as keyword arguments" do + evaluate <<-ruby do + def m(a: nil); a; end + ruby + + options = {a: 1}.freeze + -> do + m(options) + end.should raise_error(ArgumentError) + end + end + + it "assigns the last Hash to the last optional argument if the Hash contains non-Symbol keys and is not passed as keywords" do + def m(a = nil, b = {}, v: false) + [a, b, v] + end + + h = { "key" => "value" } + m(:a, h).should == [:a, h, false] + m(:a, h, v: true).should == [:a, h, true] + m(v: true).should == [nil, {}, true] + end +end + +context "when passing **nil into a method that accepts keyword arguments" do + ruby_version_is ""..."3.4" do + it "raises TypeError" do + def m(**kw) kw; end + + h = nil + -> { m(a: 1, **h) }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + -> { m(a: 1, **nil) }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + end + end + + ruby_version_is "3.4" do + it "expands nil using ** into {}" do + def m(**kw) kw; end + + h = nil + m(**h).should == {} + m(a: 1, **h).should == {a: 1} + + m(**nil).should == {} + m(a: 1, **nil).should == {a: 1} + end + end +end + +describe "A method call with a space between method name and parentheses" do + before(:each) do + def m(*args) + args + end + + def n(value, &block) + [value, block.call] + end + end + + context "when no arguments provided" do + it "assigns nil" do + args = m () + args.should == [nil] + end + end + + context "when a single argument is provided" do + it "assigns a simple expression" do + args = m (1) + args.should == [1] + end + + it "assigns an expression consisting of multiple statements" do + args = m ((0; 1)) + args.should == [1] + end + + it "assigns one single statement, without the need of parentheses" do + args = m (1 == 1 ? true : false) + args.should == [true] + end + + ruby_version_is "3.3" do + it "supports multiple statements" do + eval("m (1; 2)").should == [2] + end + end + end + + context "when multiple arguments are provided" do + it "assigns simple expressions" do + args = m (1), (2) + args.should == [1, 2] + end + + it "assigns expressions consisting of multiple statements" do + args = m ((0; 1)), ((2; 3)) + args.should == [1, 3] + end + end + + context "when the argument looks like an argument list" do + it "raises a syntax error" do + -> { + eval("m (1, 2)") + }.should raise_error(SyntaxError) + + -> { + eval("m (1, 2, 3)") + }.should raise_error(SyntaxError) + end + end + + it "allows to pass a block with curly braces" do + args = n () { :block_value } + args.should == [nil, :block_value] + + args = n (1) { :block_value } + args.should == [1, :block_value] + end + + it "allows to pass a block with do/end" do + args = n () do + :block_value + end + args.should == [nil, :block_value] + + args = n (1) do + :block_value + end + args.should == [1, :block_value] + end +end + +describe "An array-dereference method ([])" do + SpecEvaluate.desc = "for definition" + + context "received the passed-in block" do + evaluate <<-ruby do + def [](*, &b) + b.call + end + ruby + pr = proc {:ok} + + self[&pr].should == :ok + self['foo', &pr].should == :ok + self.[](&pr).should == :ok + self.[]('foo', &pr).should == :ok + end + + evaluate <<-ruby do + def [](*) + yield + end + ruby + pr = proc {:ok} + + self[&pr].should == :ok + self['foo', &pr].should == :ok + self.[](&pr).should == :ok + self.[]('foo', &pr).should == :ok + end + end +end + +describe "An endless method definition" do + context "without arguments" do + evaluate <<-ruby do + def m() = 42 + ruby + + m.should == 42 + end + + context "without parenthesis" do + evaluate <<-ruby do + def m = 42 + ruby + + m.should == 42 + end + end + end + + context "with arguments" do + evaluate <<-ruby do + def m(a, b) = a + b + ruby + + m(1, 4).should == 5 + end + end + + context "with multiline body" do + evaluate <<-ruby do + def m(n) = + if n > 2 + m(n - 2) + m(n - 1) + else + 1 + end + ruby + + m(6).should == 8 + end + end + + # tested more thoroughly in language/delegation_spec.rb + context "with args forwarding" do + evaluate <<-ruby do + def mm(word, num:) + word * num + end + + def m(...) = mm(...) + mm(...) + ruby + + m("meow", num: 2).should == "meow" * 4 + end + end +end + +describe "Keyword arguments are now separated from positional arguments" do + context "when the method has only positional parameters" do + it "treats incoming keyword arguments as positional for compatibility" do + def foo(a, b, c, hsh) + hsh[:key] + end + + foo(1, 2, 3, key: 42).should == 42 + end + end + + context "when the method takes a ** parameter" do + it "captures the passed literal keyword arguments" do + def foo(a, b, c, **hsh) + hsh[:key] + end + + foo(1, 2, 3, key: 42).should == 42 + end + + it "captures the passed ** keyword arguments" do + def foo(a, b, c, **hsh) + hsh[:key] + end + + h = { key: 42 } + foo(1, 2, 3, **h).should == 42 + end + + it "does not convert a positional Hash to keyword arguments" do + def foo(a, b, c, **hsh) + hsh[:key] + end + + -> { + foo(1, 2, 3, { key: 42 }) + }.should raise_error(ArgumentError, 'wrong number of arguments (given 4, expected 3)') + end + end + + context "when the method takes a key: parameter" do + context "when it's called with a positional Hash and no **" do + it "raises ArgumentError" do + def foo(a, b, c, key: 1) + key + end + + -> { + foo(1, 2, 3, { key: 42 }) + }.should raise_error(ArgumentError, 'wrong number of arguments (given 4, expected 3)') + end + end + + context "when it's called with **" do + it "captures the passed keyword arguments" do + def foo(a, b, c, key: 1) + key + end + + h = { key: 42 } + foo(1, 2, 3, **h).should == 42 + end + end + end +end + +describe "kwarg with omitted value in a method call" do + context "accepts short notation 'kwarg' in method call" do + evaluate <<-ruby do + def call(*args, **kwargs) = [args, kwargs] + ruby + + a, b, c = 1, 2, 3 + arr, h = call(a:) + h.should == {a: 1} + arr.should == [] + + arr, h = call(a:, b:, c:) + h.should == {a: 1, b: 2, c: 3} + arr.should == [] + + arr, h = call(a:, b: 10, c:) + h.should == {a: 1, b: 10, c: 3} + arr.should == [] + end + end + + context "with methods and local variables" do + evaluate <<-ruby do + def call(*args, **kwargs) = [args, kwargs] + + def bar + "baz" + end + + def foo(val) + call bar:, val: + end + ruby + + foo(1).should == [[], {bar: "baz", val: 1}] + end + end +end + +describe "Inside 'endless' method definitions" do + it "allows method calls without parenthesis" do + def greet(person) = "Hi, ".dup.concat person + + greet("Homer").should == "Hi, Homer" + end +end + +describe "warning about not used block argument" do + ruby_version_is "3.4" do + it "warns when passing a block argument to a method that never uses it" do + def m_that_does_not_use_block + 42 + end + + -> { + m_that_does_not_use_block { } + }.should complain( + /#{__FILE__}:#{__LINE__ - 2}: warning: the block passed to 'm_that_does_not_use_block' defined at #{__FILE__}:#{__LINE__ - 7} may be ignored/, + verbose: true) + end + + it "does not warn when passing a block argument to a method that declares a block parameter" do + def m_with_block_parameter(&block) + 42 + end + + -> { m_with_block_parameter { } }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that declares an anonymous block parameter" do + def m_with_anonymous_block_parameter(&) + 42 + end + + -> { m_with_anonymous_block_parameter { } }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that yields an implicit block parameter" do + def m_with_yield + yield 42 + end + + -> { m_with_yield { } }.should_not complain(verbose: true) + end + + it "warns when passing a block argument to a method that calls #block_given?" do + def m_with_block_given + block_given? + end + + -> { + m_with_block_given { } + }.should complain( + /#{__FILE__}:#{__LINE__ - 2}: warning: the block passed to 'm_with_block_given' defined at #{__FILE__}:#{__LINE__ - 7} may be ignored/, + verbose: true) + end + + it "does not warn when passing a block argument to a method that calls super" do + parent = Class.new do + def m + end + end + + child = Class.new(parent) do + def m + super + end + end + + obj = child.new + -> { obj.m { } }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that calls super(...)" do + parent = Class.new do + def m(a) + end + end + + child = Class.new(parent) do + def m(...) + super(...) + end + end + + obj = child.new + -> { obj.m(42) { } }.should_not complain(verbose: true) + end + + it "does not warn when called #initialize()" do + klass = Class.new do + def initialize + end + end + + -> { klass.new {} }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that calls super()" do + parent = Class.new do + def m + end + end + + child = Class.new(parent) do + def m + super() + end + end + + obj = child.new + -> { obj.m { } }.should_not complain(verbose: true) + end + + it "warns only once per call site" do + def m_that_does_not_use_block + 42 + end + + def call_m_that_does_not_use_block + m_that_does_not_use_block {} + end + + -> { + m_that_does_not_use_block { } + }.should complain(/the block passed to 'm_that_does_not_use_block' defined at .+ may be ignored/, verbose: true) + + -> { + m_that_does_not_use_block { } + }.should_not complain(verbose: true) + end + + it "can be disabled with :strict_unused_block warning category" do + def m_that_does_not_use_block + 42 + end + + # ensure that warning is emitted + -> { m_that_does_not_use_block { } }.should complain(verbose: true) + + warn_strict_unused_block = Warning[:strict_unused_block] + Warning[:strict_unused_block] = false + begin + -> { m_that_does_not_use_block { } }.should_not complain(verbose: true) + ensure + Warning[:strict_unused_block] = warn_strict_unused_block + end + end + + it "can be enabled with :strict_unused_block = true warning category in not verbose mode" do + def m_that_does_not_use_block + 42 + end + + warn_strict_unused_block = Warning[:strict_unused_block] + Warning[:strict_unused_block] = true + begin + -> { + m_that_does_not_use_block { } + }.should complain(/the block passed to 'm_that_does_not_use_block' defined at .+ may be ignored/) + ensure + Warning[:strict_unused_block] = warn_strict_unused_block + end + end + end +end |
