diff options
Diffstat (limited to 'spec/ruby/language/block_spec.rb')
| -rw-r--r-- | spec/ruby/language/block_spec.rb | 376 |
1 files changed, 265 insertions, 111 deletions
diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb index 90329e2f6f..5bdb993aea 100644 --- a/spec/ruby/language/block_spec.rb +++ b/spec/ruby/language/block_spec.rb @@ -13,7 +13,7 @@ describe "A block yielded a single" do it "receives the identical Array object" do ary = [1, 2] - m(ary) { |a| a }.should equal(ary) + m(ary) { |a| a }.should.equal?(ary) end it "assigns the Array to a single rest argument" do @@ -40,17 +40,40 @@ describe "A block yielded a single" do m([1, 2]) { |a=5, b, c, d| [a, b, c, d] }.should == [5, 1, 2, nil] end - ruby_version_is "3.2" do - it "does not autosplat single argument to required arguments when a keyword rest argument is present" do - m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}] - end + it "assigns elements to pre arguments" do + m([1, 2]) { |a, b, c, d=5| [a, b, c, d] }.should == [1, 2, nil, 5] end - ruby_version_is ''..."3.2" do - # https://bugs.ruby-lang.org/issues/18633 - it "autosplats single argument to required arguments when a keyword rest argument is present" do - m([1, 2]) { |a, **k| [a, k] }.should == [1, {}] - end + it "assigns elements to pre and post arguments" do + m([1 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 5, 6, nil, nil] + m([1, 2 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 5, 6, 2, nil] + m([1, 2, 3 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 5, 6, 2, 3] + m([1, 2, 3, 4 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 2, 6, 3, 4] + m([1, 2, 3, 4, 5 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 2, 3, 4, 5] + m([1, 2, 3, 4, 5, 6]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 2, 3, 4, 5] + end + + it "assigns elements to pre and post arguments when *rest is present" do + m([1 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 5, 6, [], nil, nil] + m([1, 2 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 5, 6, [], 2, nil] + m([1, 2, 3 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 5, 6, [], 2, 3] + m([1, 2, 3, 4 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 2, 6, [], 3, 4] + m([1, 2, 3, 4, 5 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 2, 3, [], 4, 5] + m([1, 2, 3, 4, 5, 6]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 2, 3, [4], 5, 6] + end + + it "does not autosplat single argument to required arguments when a keyword rest argument is present" do + m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}] + end + + it "does not autosplat single argument to required arguments when keyword arguments are present" do + m([1, 2]) { |a, b: :b, c: :c| [a, b, c] }.should == [[1, 2], :b, :c] + end + + it "raises error when required keyword arguments are present" do + -> { + m([1, 2]) { |a, b:, c:| [a, b, c] } + }.should.raise(ArgumentError, "missing keywords: :b, :c") end it "assigns elements to mixed argument types" do @@ -169,6 +192,22 @@ describe "A block yielded a single" do m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] end + it "calls #respond_to? on a BasicObject to check if object has method #to_ary" do + ScratchPad.record [] + obj = BasicObject.new + def obj.respond_to?(name, *) + ScratchPad << [:respond_to?, name] + name == :to_ary ? true : super + end + def obj.to_ary + ScratchPad << :to_ary + [1, 2] + end + + m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] + ScratchPad.recorded.should == [[:respond_to?, :to_ary], :to_ary] + end + it "receives the object if it does not respond to #respond_to?" do obj = BasicObject.new @@ -195,14 +234,14 @@ describe "A block yielded a single" do obj = mock("destructure block arguments") obj.should_receive(:to_ary).and_return(1) - -> { m(obj) { |a, b| } }.should raise_error(TypeError) + -> { m(obj) { |a, b| } }.should.raise(TypeError) end it "raises error transparently if #to_ary raises error on its own" do obj = Object.new def obj.to_ary; raise "Exception raised in #to_ary" end - -> { m(obj) { |a, b| } }.should raise_error(RuntimeError, "Exception raised in #to_ary") + -> { m(obj) { |a, b| } }.should.raise(RuntimeError, "Exception raised in #to_ary") end end end @@ -252,7 +291,7 @@ describe "A block" do end it "may include a rescue clause" do - eval("@y.z do raise ArgumentError; rescue ArgumentError; 7; end").should == 7 + @y.z do raise ArgumentError; rescue ArgumentError; 7; end.should == 7 end end @@ -266,13 +305,13 @@ describe "A block" do end it "may include a rescue clause" do - eval('@y.z do || raise ArgumentError; rescue ArgumentError; 7; end').should == 7 + @y.z do || raise ArgumentError; rescue ArgumentError; 7; end.should == 7 end end describe "taking |a| arguments" do it "assigns nil to the argument when no values are yielded" do - @y.z { |a| a }.should be_nil + @y.z { |a| a }.should == nil end it "assigns the value yielded to the argument" do @@ -283,7 +322,7 @@ describe "A block" do obj = mock("block yield to_ary") obj.should_not_receive(:to_ary) - @y.s(obj) { |a| a }.should equal(obj) + @y.s(obj) { |a| a }.should.equal?(obj) end it "assigns the first value yielded to the argument" do @@ -295,7 +334,7 @@ describe "A block" do end it "may include a rescue clause" do - eval('@y.s(1) do |x| raise ArgumentError; rescue ArgumentError; 7; end').should == 7 + @y.s(1) do |x| raise ArgumentError; rescue ArgumentError; 7; end.should == 7 end end @@ -359,16 +398,15 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |a, b| } }.should raise_error(TypeError) + -> { @y.s(obj) { |a, b| } }.should.raise(TypeError) end it "raises the original exception if #to_ary raises an exception" do obj = mock("block yield to_ary raising an exception") obj.should_receive(:to_ary).and_raise(ZeroDivisionError) - -> { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError) + -> { @y.s(obj) { |a, b| } }.should.raise(ZeroDivisionError) end - end describe "taking |a, *b| arguments" do @@ -423,7 +461,7 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |a, *b| } }.should raise_error(TypeError) + -> { @y.s(obj) { |a, *b| } }.should.raise(TypeError) end end @@ -501,7 +539,7 @@ describe "A block" do describe "taking |a, | arguments" do it "assigns nil to the argument when no values are yielded" do - @y.z { |a, | a }.should be_nil + @y.z { |a, | a }.should == nil end it "assigns the argument a single value yielded" do @@ -517,7 +555,7 @@ describe "A block" do end it "assigns nil to the argument when passed an empty Array" do - @y.s([]) { |a, | a }.should be_nil + @y.s([]) { |a, | a }.should == nil end it "assigns the argument the first element of the Array when passed a single Array" do @@ -548,7 +586,7 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |a, | } }.should raise_error(TypeError) + -> { @y.s(obj) { |a, | } }.should.raise(TypeError) end end @@ -590,7 +628,7 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |(a, b)| } }.should raise_error(TypeError) + -> { @y.s(obj) { |(a, b)| } }.should.raise(TypeError) end end @@ -631,7 +669,7 @@ describe "A block" do obj = mock("block yield to_ary invalid") obj.should_receive(:to_ary).and_return(1) - -> { @y.s(obj) { |(a, b), c| } }.should raise_error(TypeError) + -> { @y.s(obj) { |(a, b), c| } }.should.raise(TypeError) end end @@ -690,15 +728,51 @@ describe "A block" do describe "taking identically-named arguments" do it "raises a SyntaxError for standard arguments" do - -> { eval "lambda { |x,x| }" }.should raise_error(SyntaxError) - -> { eval "->(x,x) {}" }.should raise_error(SyntaxError) - -> { eval "Proc.new { |x,x| }" }.should raise_error(SyntaxError) + -> { eval "lambda { |x,x| }" }.should.raise(SyntaxError) + -> { eval "->(x,x) {}" }.should.raise(SyntaxError) + -> { eval "Proc.new { |x,x| }" }.should.raise(SyntaxError) end it "accepts unnamed arguments" do - eval("lambda { |_,_| }").should be_an_instance_of(Proc) - eval("->(_,_) {}").should be_an_instance_of(Proc) - eval("Proc.new { |_,_| }").should be_an_instance_of(Proc) + lambda { |_,_| }.should.instance_of?(Proc) # rubocop:disable Style/Lambda + -> _,_ {}.should.instance_of?(Proc) + Proc.new { |_,_| }.should.instance_of?(Proc) + end + end + + describe 'pre and post parameters' do + it "assigns nil to unassigned required arguments" do + proc { |a, *b, c, d| [a, b, c, d] }.call(1, 2).should == [1, [], 2, nil] + end + + it "assigns elements to optional arguments" do + proc { |a=5, b=4, c=3| [a, b, c] }.call(1, 2).should == [1, 2, 3] + end + + it "assigns elements to post arguments" do + proc { |a=5, b, c, d| [a, b, c, d] }.call(1, 2).should == [5, 1, 2, nil] + end + + it "assigns elements to pre arguments" do + proc { |a, b, c, d=5| [a, b, c, d] }.call(1, 2).should == [1, 2, nil, 5] + end + + it "assigns elements to pre and post arguments" do + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1 ).should == [1, 5, 6, nil, nil] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2 ).should == [1, 5, 6, 2, nil] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3 ).should == [1, 5, 6, 2, 3] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3, 4 ).should == [1, 2, 6, 3, 4] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3, 4, 5 ).should == [1, 2, 3, 4, 5] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3, 4, 5, 6).should == [1, 2, 3, 4, 5] + end + + it "assigns elements to pre and post arguments when *rest is present" do + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1 ).should == [1, 5, 6, [], nil, nil] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2 ).should == [1, 5, 6, [], 2, nil] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3 ).should == [1, 5, 6, [], 2, 3] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3, 4 ).should == [1, 2, 6, [], 3, 4] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3, 4, 5 ).should == [1, 2, 3, [], 4, 5] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3, 4, 5, 6).should == [1, 2, 3, [4], 5, 6] end end end @@ -713,29 +787,29 @@ describe "Block-local variables" do end it "can not have the same name as one of the standard parameters" do - -> { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError) - -> { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; foo| }" }.should.raise(SyntaxError) + -> { eval "[1].each {|foo, bar; glark, bar| }" }.should.raise(SyntaxError) end it "can not be prefixed with an asterisk" do - -> { eval "[1].each {|foo; *bar| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; *bar| }" }.should.raise(SyntaxError) -> do eval "[1].each {|foo, bar; glark, *fnord| }" - end.should raise_error(SyntaxError) + end.should.raise(SyntaxError) end it "can not be prefixed with an ampersand" do - -> { eval "[1].each {|foo; &bar| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; &bar| }" }.should.raise(SyntaxError) -> do eval "[1].each {|foo, bar; glark, &fnord| }" - end.should raise_error(SyntaxError) + end.should.raise(SyntaxError) end it "can not be assigned default values" do - -> { eval "[1].each {|foo; bar=1| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; bar=1| }" }.should.raise(SyntaxError) -> do eval "[1].each {|foo, bar; glark, fnord=:fnord| }" - end.should raise_error(SyntaxError) + end.should.raise(SyntaxError) end it "need not be preceded by standard parameters" do @@ -744,8 +818,8 @@ describe "Block-local variables" do end it "only allow a single semi-colon in the parameter list" do - -> { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError) - -> { eval "[1].each {|; bar; glark| }" }.should raise_error(SyntaxError) + -> { eval "[1].each {|foo; bar; glark| }" }.should.raise(SyntaxError) + -> { eval "[1].each {|; bar; glark| }" }.should.raise(SyntaxError) end it "override shadowed variables from the outer scope" do @@ -770,21 +844,21 @@ describe "Block-local variables" do end it "are not automatically instantiated in the outer scope" do - defined?(glark).should be_nil + defined?(glark).should == nil [1].each {|;glark| 1} - defined?(glark).should be_nil + defined?(glark).should == nil end it "are automatically instantiated in the block" do [1].each do |;glark| - glark.should be_nil + glark.should == nil end end it "are visible in deeper scopes before initialization" do [1].each {|;glark| [1].each { - defined?(glark).should_not be_nil + defined?(glark).should_not == nil glark = 1 } glark.should == 1 @@ -812,7 +886,7 @@ describe "Post-args" do -> *a, b do [a, b] end.call - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "are assigned to nil when not enough arguments are given to a proc" do @@ -883,24 +957,27 @@ describe "Post-args" do end describe "with a circular argument reference" do - it "raises a SyntaxError if using an existing local with the same name as the argument" do - a = 1 - -> { - @proc = eval "proc { |a=a| a }" - }.should raise_error(SyntaxError) + ruby_version_is ""..."3.4" do + it "raises a SyntaxError if using the argument in its default value" do + a = 1 + -> { + eval "proc { |a=a| a }" + }.should.raise(SyntaxError) + end end - it "raises a SyntaxError if there is an existing method with the same name as the argument" do - def a; 1; end - -> { - @proc = eval "proc { |a=a| a }" - }.should raise_error(SyntaxError) + ruby_version_is "3.4" do + it "is nil if using the argument in its default value" do + -> { + eval "proc { |a=a| a }.call" + }.call.should == nil + end end + end - it "calls an existing method with the same name as the argument if explicitly using ()" do - def a; 1; end - proc { |a=a()| a }.call.should == 1 - end + it "calls an existing method with the same name as the argument if explicitly using ()" do + def a; 1; end + proc { |a=a()| a }.call.should == 1 end end @@ -919,76 +996,153 @@ describe "Post-args" do end end +# tested more thoroughly in language/delegation_spec.rb describe "Anonymous block forwarding" do - ruby_version_is "3.1" do - it "forwards blocks to other functions that formally declare anonymous blocks" do - eval <<-EOF - def b(&); c(&) end - def c(&); yield :non_null end - EOF + it "forwards blocks to other method that formally declares anonymous block" do + def b(&); c(&) end + def c(&); yield :non_null end + + b { |c| c }.should == :non_null + end + + it "requires the anonymous block parameter to be declared if directly passing a block" do + -> { eval "def a; b(&); end; def b; end" }.should.raise(SyntaxError) + end - b { |c| c }.should == :non_null + it "works when it's the only declared parameter" do + def inner; yield end + def block_only(&); inner(&) end + + block_only { 1 }.should == 1 + end + + it "works alongside positional parameters" do + def inner; yield end + def pos(arg1, &); inner(&) end + + pos(:a) { 1 }.should == 1 + end + + it "works alongside positional arguments and splatted keyword arguments" do + def inner; yield end + def pos_kwrest(arg1, **kw, &); inner(&) end + + pos_kwrest(:a, arg: 3) { 1 }.should == 1 + end + + it "works alongside positional arguments and disallowed keyword arguments" do + def inner; yield end + def no_kw(arg1, **nil, &); inner(&) end + + no_kw(:a) { 1 }.should == 1 + end + + it "works alongside explicit keyword arguments" do + eval <<-EOF + def inner; yield end + def rest_kw(*a, kwarg: 1, &); inner(&) end + def kw(kwarg: 1, &); inner(&) end + def pos_kw_kwrest(arg1, kwarg: 1, **kw, &); inner(&) end + def pos_rkw(arg1, kwarg1:, &); inner(&) end + def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &); inner(&) end + def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &); inner(&) end + EOF + + rest_kw { 1 }.should == 1 + kw { 1 }.should == 1 + pos_kw_kwrest(:a) { 1 }.should == 1 + pos_rkw(:a, kwarg1: 3) { 1 }.should == 1 + all(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1 + all_kwrest(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1 + end +end + +describe "`it` calls without arguments in a block" do + ruby_version_is ""..."3.4" do + it "emits a deprecation warning" do + -> { + eval "proc { it }" + }.should complain(/warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it\(\) or self.it/) end - it "requires the anonymous block parameter to be declared if directly passing a block" do - -> { eval "def a; b(&); end; def b; end" }.should raise_error(SyntaxError) + it "emits a deprecation warning if numbered parameters are used" do + -> { + eval "proc { it; _1 }" + }.should complain(/warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it\(\) or self.it/) end - it "works when it's the only declared parameter" do - eval <<-EOF - def inner; yield end - def block_only(&); inner(&) end - EOF + it "does not emit a deprecation warning when a block has parameters" do + -> { eval "proc { |a, b| it }" }.should_not complain + -> { eval "proc { |*rest| it }" }.should_not complain + -> { eval "proc { |*| it }" }.should_not complain + -> { eval "proc { |a:, b:| it }" }.should_not complain + -> { eval "proc { |**kw| it }" }.should_not complain + -> { eval "proc { |**| it }" }.should_not complain + -> { eval "proc { |&block| it }" }.should_not complain + -> { eval "proc { |&| it }" }.should_not complain + -> { eval "proc { || it }" }.should_not complain + end - block_only { 1 }.should == 1 + it "does not emit a deprecation warning when `it` calls with arguments" do + -> { eval "proc { it(42) }" }.should_not complain + -> { eval "proc { it 42 }" }.should_not complain end - it "works alongside positional parameters" do - eval <<-EOF - def inner; yield end - def pos(arg1, &); inner(&) end - EOF + it "does not emit a deprecation warning when `it` calls with a block" do + -> { eval "proc { it {} }" }.should_not complain + end - pos(:a) { 1 }.should == 1 + it "does not emit a deprecation warning when a local variable inside the block named `it` exists" do + -> { eval "proc { it = 42; it }" }.should_not complain end - it "works alongside positional arguments and splatted keyword arguments" do - eval <<-EOF - def inner; yield end - def pos_kwrest(arg1, **kw, &); inner(&) end - EOF + it "does not emit a deprecation warning when `it` calls with explicit empty arguments list" do + -> { eval "proc { it() }" }.should_not complain + end - pos_kwrest(:a, arg: 3) { 1 }.should == 1 + it "calls the method `it` if defined" do + o = Object.new + def o.it + 21 + end + suppress_warning do + o.instance_eval("proc { it * 2 }").call(1).should == 42 + end end + end - it "works alongside positional arguments and disallowed keyword arguments" do - eval <<-EOF - def inner; yield end - def no_kw(arg1, **nil, &); inner(&) end + ruby_version_is "4.1" do + it "works alongside disallowed block argument" do + no_block = eval <<-EOF + proc {|arg1, &nil| arg1} EOF - no_kw(:a) { 1 }.should == 1 + no_block.call(:a).should == :a + -> { no_block.call(:a) {} }.should.raise(ArgumentError, 'no block accepted') end end +end - ruby_version_is "3.2" do - it "works alongside explicit keyword arguments" do - eval <<-EOF - def inner; yield end - def rest_kw(*a, kwarg: 1, &); inner(&) end - def kw(kwarg: 1, &); inner(&) end - def pos_kw_kwrest(arg1, kwarg: 1, **kw, &); inner(&) end - def pos_rkw(arg1, kwarg1:, &); inner(&) end - def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &); inner(&) end - def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &); inner(&) end - EOF +# Duplicates specs in language/it_parameter_spec.rb +# Need them here to run on Ruby versions prior 3.4 +# TODO: remove when the minimal supported Ruby version is 3.4 +describe "if `it` is defined as a variable" do + it "treats `it` as a captured variable if defined outside of a block" do + it = 5 + proc { it }.call(0).should == 5 + end - rest_kw { 1 }.should == 1 - kw { 1 }.should == 1 - pos_kw_kwrest(:a) { 1 }.should == 1 - pos_rkw(:a, kwarg1: 3) { 1 }.should == 1 - all(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1 - all_kwrest(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1 - end + it "treats `it` as a local variable if defined inside of a block" do + proc { it = 5; it }.call(0).should == 5 + end +end + +describe "Block-parameter destructuring" do + it "does not warn about unused inner names in verbose mode" do + -> { + eval <<~RUBY, binding, __FILE__, __LINE__ + 1 + proc { |key, (val1, val2)| [key, val2] } + RUBY + }.should_not complain(verbose: true) end end |
