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

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

ruby_version_is "3.1" do
  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 = eval('call a:')
        h.should == {a: 1}
        arr.should == []

        arr, h = eval('call(a:, b:, c:)')
        h.should == {a: 1, b: 2, c: 3}
        arr.should == []

        arr, h = eval('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
      eval <<-ruby
        def greet(person) = "Hi, ".dup.concat person
      ruby

      greet("Homer").should == "Hi, Homer"
    end
  end
end
