diff options
Diffstat (limited to 'spec/ruby/language')
48 files changed, 3080 insertions, 3288 deletions
diff --git a/spec/ruby/language/END_spec.rb b/spec/ruby/language/END_spec.rb index c84f0cc9ac..762a8db0c0 100644 --- a/spec/ruby/language/END_spec.rb +++ b/spec/ruby/language/END_spec.rb @@ -1,33 +1,15 @@ require_relative '../spec_helper' -require_relative '../shared/kernel/at_exit' describe "The END keyword" do - it_behaves_like :kernel_at_exit, :END - it "runs only once for multiple calls" do ruby_exe("10.times { END { puts 'foo' }; } ").should == "foo\n" end - it "is affected by the toplevel assignment" do - ruby_exe("foo = 'foo'; END { puts foo }").should == "foo\n" - end - - it "warns when END is used in a method" do - ruby_exe(<<~ruby, args: "2>&1").should =~ /warning: END in method; use at_exit/ - def foo - END { } - end - ruby + it "runs last in a given code unit" do + ruby_exe("END { puts 'bar' }; puts'foo'; ").should == "foo\nbar\n" end - context "END blocks and at_exit callbacks are mixed" do - it "runs them all in reverse order of registration" do - ruby_exe(<<~ruby).should == "at_exit#2\nEND#2\nat_exit#1\nEND#1\n" - END { puts 'END#1' } - at_exit { puts 'at_exit#1' } - END { puts 'END#2' } - at_exit { puts 'at_exit#2' } - ruby - end + it "runs multiple ends in LIFO order" do + ruby_exe("END { puts 'foo' }; END { puts 'bar' }").should == "bar\nfoo\n" end end diff --git a/spec/ruby/language/alias_spec.rb b/spec/ruby/language/alias_spec.rb index 61fddb0184..d1d06e3fac 100644 --- a/spec/ruby/language/alias_spec.rb +++ b/spec/ruby/language/alias_spec.rb @@ -52,15 +52,6 @@ describe "The alias keyword" do @obj.a.should == 5 end - it "works with an interpolated symbol with non-literal embedded expression on the left-hand side" do - @meta.class_eval do - eval %Q{ - alias :"#{'a' + ''.to_s}" value - } - end - @obj.a.should == 5 - end - it "works with a simple symbol on the right-hand side" do @meta.class_eval do alias a :value @@ -89,15 +80,6 @@ describe "The alias keyword" do @obj.a.should == 5 end - it "works with an interpolated symbol with non-literal embedded expression on the right-hand side" do - @meta.class_eval do - eval %Q{ - alias a :"#{'value' + ''.to_s}" - } - end - @obj.a.should == 5 - end - it "adds the new method to the list of methods" do original_methods = @obj.methods @meta.class_eval do @@ -252,7 +234,7 @@ describe "The alias keyword" do it "on top level defines the alias on Object" do # because it defines on the default definee / current module - ruby_exe("def foo; end; alias bla foo; print method(:bla).owner").should == "Object" + ruby_exe("def foo; end; alias bla foo; print method(:bla).owner", escape: true).should == "Object" end it "raises a NameError when passed a missing name" do @@ -261,19 +243,6 @@ describe "The alias keyword" do e.class.should == NameError } end - - it "defines the method on the aliased class when the original method is from a parent class" do - parent = Class.new do - def parent_method - end - end - child = Class.new(parent) do - alias parent_method_alias parent_method - end - - child.instance_method(:parent_method_alias).owner.should == child - child.instance_methods(false).should include(:parent_method_alias) - end end describe "The alias keyword" do diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb index 90329e2f6f..b2a3cc84c4 100644 --- a/spec/ruby/language/block_spec.rb +++ b/spec/ruby/language/block_spec.rb @@ -40,31 +40,80 @@ 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], {}] + it "assigns elements to required arguments when a keyword rest argument is present" do + m([1, 2]) { |a, **k| [a, k] }.should == [1, {}] + end + + ruby_version_is ''..."3.0" do + it "assigns elements to mixed argument types" do + suppress_keyword_warning do + result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] } + result.should == [1, 2, [], 3, 2, {x: 9}] + end + end + + it "assigns symbol keys from a Hash to keyword arguments" do + suppress_keyword_warning do + result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] } + result.should == [{"a" => 1}, a: 10] + end + end + + it "assigns symbol keys from a Hash returned by #to_hash to keyword arguments" do + suppress_keyword_warning do + obj = mock("coerce block keyword arguments") + obj.should_receive(:to_hash).and_return({"a" => 1, b: 2}) + + result = m([obj]) { |a=nil, **b| [a, b] } + result.should == [{"a" => 1}, b: 2] + end end 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, {}] + ruby_version_is "3.0" do + it "assigns elements to mixed argument types" do + result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] } + result.should == [1, 2, [3], {x: 9}, 2, {}] + end + + it "does not treat final Hash as keyword arguments and does not autosplat" do + result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] } + result.should == [[{"a" => 1, a: 10}], {}] + end + + it "does not call #to_hash on final argument to get keyword arguments and does not autosplat" do + suppress_keyword_warning do + obj = mock("coerce block keyword arguments") + obj.should_not_receive(:to_hash) + + result = m([obj]) { |a=nil, **b| [a, b] } + result.should == [[obj], {}] + end end end - it "assigns elements to mixed argument types" do - result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] } - result.should == [1, 2, [3], {x: 9}, 2, {}] + ruby_version_is ""..."2.7" do + it "calls #to_hash on the argument and uses resulting hash as first argument when optional argument and keyword argument accepted" do + obj = mock("coerce block keyword arguments") + obj.should_receive(:to_hash).and_return({"a" => 1, "b" => 2}) + + result = m([obj]) { |a=nil, **b| [a, b] } + result.should == [{"a" => 1, "b" => 2}, {}] + end end - it "does not treat final Hash as keyword arguments and does not autosplat" do - result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] } - result.should == [[{"a" => 1, a: 10}], {}] + ruby_version_is "2.7"...'3.0' do + it "calls #to_hash on the argument but ignores result when optional argument and keyword argument accepted" do + obj = mock("coerce block keyword arguments") + obj.should_receive(:to_hash).and_return({"a" => 1, "b" => 2}) + + result = m([obj]) { |a=nil, **b| [a, b] } + result.should == [obj, {}] + end end - it "does not call #to_hash on final argument to get keyword arguments and does not autosplat" do - suppress_keyword_warning do + ruby_version_is "3.0" do + it "does not call #to_hash on the argument when optional argument and keyword argument accepted and does not autosplat" do obj = mock("coerce block keyword arguments") obj.should_not_receive(:to_hash) @@ -73,42 +122,102 @@ describe "A block yielded a single" do end end - it "does not call #to_hash on the argument when optional argument and keyword argument accepted and does not autosplat" do - obj = mock("coerce block keyword arguments") - obj.should_not_receive(:to_hash) + describe "when non-symbol keys are in a keyword arguments Hash" do + ruby_version_is ""..."3.0" do + it "separates non-symbol keys and symbol keys" do + suppress_keyword_warning do + result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] } + result.should == [{"a" => 10}, {b: 2}] + end + end + end + ruby_version_is "3.0" do + it "does not separate non-symbol keys and symbol keys and does not autosplat" do + suppress_keyword_warning do + result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] } + result.should == [[{"a" => 10, b: 2}], {}] + end + end + end + end - result = m([obj]) { |a=nil, **b| [a, b] } - result.should == [[obj], {}] + ruby_version_is ""..."3.0" do + it "does not treat hashes with string keys as keyword arguments" do + result = m(["a" => 10]) { |a = nil, **b| [a, b] } + result.should == [{"a" => 10}, {}] + end end - describe "when non-symbol keys are in a keyword arguments Hash" do - it "does not separate non-symbol keys and symbol keys and does not autosplat" do + ruby_version_is "3.0" do + it "does not treat hashes with string keys as keyword arguments and does not autosplat" do + result = m(["a" => 10]) { |a = nil, **b| [a, b] } + result.should == [[{"a" => 10}], {}] + end + end + + ruby_version_is ''...'3.0' do + it "calls #to_hash on the last element if keyword arguments are present" do suppress_keyword_warning do - result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] } - result.should == [[{"a" => 10, b: 2}], {}] + obj = mock("destructure block keyword arguments") + obj.should_receive(:to_hash).and_return({x: 9}) + + result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } + result.should == [1, [2], 3, {x: 9}] end end - end - it "does not treat hashes with string keys as keyword arguments and does not autosplat" do - result = m(["a" => 10]) { |a = nil, **b| [a, b] } - result.should == [[{"a" => 10}], {}] - end + it "assigns the last element to a non-keyword argument if #to_hash returns nil" do + suppress_keyword_warning do + obj = mock("destructure block keyword arguments") + obj.should_receive(:to_hash).and_return(nil) - it "does not call #to_hash on the last element if keyword arguments are present" do - obj = mock("destructure block keyword arguments") - obj.should_not_receive(:to_hash) + result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } + result.should == [1, [2, 3], obj, {}] + end + end + + it "calls #to_hash on the last element when there are more arguments than parameters" do + suppress_keyword_warning do + x = mock("destructure matching block keyword argument") + x.should_receive(:to_hash).and_return({x: 9}) + + result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] } + result.should == [1, 2, 3, {x: 9}] + end + end - result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } - result.should == [1, [2, 3], obj, {}] + it "raises a TypeError if #to_hash does not return a Hash" do + obj = mock("destructure block keyword arguments") + obj.should_receive(:to_hash).and_return(1) + + -> { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(TypeError) + end + + it "raises the error raised inside #to_hash" do + obj = mock("destructure block keyword arguments") + error = RuntimeError.new("error while converting to a hash") + obj.should_receive(:to_hash).and_raise(error) + + -> { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(error) + end end - it "does not call #to_hash on the last element when there are more arguments than parameters" do - x = mock("destructure matching block keyword argument") - x.should_not_receive(:to_hash) + ruby_version_is '3.0' do + it "does not call #to_hash on the last element if keyword arguments are present" do + obj = mock("destructure block keyword arguments") + obj.should_not_receive(:to_hash) + + result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } + result.should == [1, [2, 3], obj, {}] + end + + it "does not call #to_hash on the last element when there are more arguments than parameters" do + x = mock("destructure matching block keyword argument") + x.should_not_receive(:to_hash) - result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] } - result.should == [1, 2, 3, {}] + result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] } + result.should == [1, 2, 3, {}] + end end it "does not call #to_ary on the Array" do @@ -155,55 +264,12 @@ describe "A block yielded a single" do m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil] end - it "receives the object if it does not respond to #to_ary" do - obj = Object.new - - m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil] - end - - it "calls #respond_to? to check if object has method #to_ary" do - obj = mock("destructure block arguments") - obj.should_receive(:respond_to?).with(:to_ary, true).and_return(true) - obj.should_receive(:to_ary).and_return([1, 2]) - - m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] - end - - it "receives the object if it does not respond to #respond_to?" do - obj = BasicObject.new - - m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil] - end - - it "calls #to_ary on the object when it is defined dynamically" do - obj = Object.new - def obj.method_missing(name, *args, &block) - if name == :to_ary - [1, 2] - else - super - end - end - def obj.respond_to_missing?(name, include_private) - name == :to_ary - end - - m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] - end - it "raises a TypeError if #to_ary does not return an Array" do obj = mock("destructure block arguments") obj.should_receive(:to_ary).and_return(1) -> { m(obj) { |a, b| } }.should raise_error(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") - end end end @@ -883,18 +949,38 @@ 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 ''...'2.7' do + it "warns and uses a nil value when there is an existing local variable with same name" do + a = 1 + -> { + @proc = eval "proc { |a=a| a }" + }.should complain(/circular argument reference/) + @proc.call.should == nil + end + + it "warns and uses a nil value when there is an existing method with same name" do + def a; 1; end + -> { + @proc = eval "proc { |a=a| a }" + }.should complain(/circular argument reference/) + @proc.call.should == nil + 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 '2.7' 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) + 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) + end end it "calls an existing method with the same name as the argument if explicitly using ()" do @@ -918,77 +1004,3 @@ describe "Post-args" do end end end - -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 - - 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_error(SyntaxError) - end - - it "works when it's the only declared parameter" do - eval <<-EOF - def inner; yield end - def block_only(&); inner(&) end - EOF - - block_only { 1 }.should == 1 - end - - it "works alongside positional parameters" do - eval <<-EOF - def inner; yield end - def pos(arg1, &); inner(&) end - EOF - - pos(:a) { 1 }.should == 1 - 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 - - pos_kwrest(:a, arg: 3) { 1 }.should == 1 - end - - it "works alongside positional arguments and disallowed keyword arguments" do - eval <<-EOF - def inner; yield end - def no_kw(arg1, **nil, &); inner(&) end - EOF - - no_kw(:a) { 1 }.should == 1 - 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 - - 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 -end diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb index 1a3925c9c6..410cb9afb1 100644 --- a/spec/ruby/language/case_spec.rb +++ b/spec/ruby/language/case_spec.rb @@ -103,7 +103,7 @@ describe "The 'case'-construct" do $1.should == "42" end - it "tests with a string interpolated in a regexp" do + it "tests with a regexp interpolated within another regexp" do digits = '\d+' case "foo44" when /oo(#{digits})/ @@ -116,7 +116,7 @@ describe "The 'case'-construct" do $1.should == "44" end - it "tests with a regexp interpolated within another regexp" do + it "tests with a string interpolated in a regexp" do digits_regexp = /\d+/ case "foo43" when /oo(#{digits_regexp})/ @@ -156,15 +156,6 @@ describe "The 'case'-construct" do end.should == "foo" end - it "tests an empty array" do - case [] - when [] - 'foo' - else - 'bar' - end.should == 'foo' - end - it "expands arrays to lists of values" do case 'z' when *['a', 'b', 'c', 'd'] @@ -329,6 +320,49 @@ describe "The 'case'-construct" do 100 end.should == 100 end +end + +describe "The 'case'-construct with no target expression" do + it "evaluates the body of the first clause when at least one of its condition expressions is true" do + case + when true, false; 'foo' + end.should == 'foo' + end + + it "evaluates the body of the first when clause that is not false/nil" do + case + when false; 'foo' + when 2; 'bar' + when 1 == 1; 'baz' + end.should == 'bar' + + case + when false; 'foo' + when nil; 'foo' + when 1 == 1; 'bar' + end.should == 'bar' + end + + it "evaluates the body of the else clause if all when clauses are false/nil" do + case + when false; 'foo' + when nil; 'foo' + when 1 == 2; 'bar' + else 'baz' + end.should == 'baz' + end + + it "evaluates multiple conditional expressions as a boolean disjunction" do + case + when true, false; 'foo' + else 'bar' + end.should == 'foo' + + case + when false, true; 'foo' + else 'bar' + end.should == 'foo' + end it "evaluates true as only 'true' when true is the first clause" do case 1 @@ -391,58 +425,6 @@ describe "The 'case'-construct" do end.should == :called end - it "only matches last value in complex expressions within ()" do - case 'a' - when ('a'; 'b') - :wrong_called - when ('b'; 'a') - :called - end.should == :called - end -end - -describe "The 'case'-construct with no target expression" do - it "evaluates the body of the first clause when at least one of its condition expressions is true" do - case - when true, false; 'foo' - end.should == 'foo' - end - - it "evaluates the body of the first when clause that is not false/nil" do - case - when false; 'foo' - when 2; 'bar' - when 1 == 1; 'baz' - end.should == 'bar' - - case - when false; 'foo' - when nil; 'foo' - when 1 == 1; 'bar' - end.should == 'bar' - end - - it "evaluates the body of the else clause if all when clauses are false/nil" do - case - when false; 'foo' - when nil; 'foo' - when 1 == 2; 'bar' - else 'baz' - end.should == 'baz' - end - - it "evaluates multiple conditional expressions as a boolean disjunction" do - case - when true, false; 'foo' - else 'bar' - end.should == 'foo' - - case - when false, true; 'foo' - else 'bar' - end.should == 'foo' - end - # Homogeneous cases are often optimized to avoid === using a jump table, and should be tested separately. # See https://github.com/jruby/jruby/issues/6440 it "handles homogeneous cases" do @@ -451,13 +433,4 @@ describe "The 'case'-construct with no target expression" do when 2; 'bar' end.should == 'foo' end - - it "expands arrays to lists of values" do - case - when *[false] - "foo" - when *[true] - "bar" - end.should == "bar" - end end diff --git a/spec/ruby/language/class_spec.rb b/spec/ruby/language/class_spec.rb index eab3cd0651..83db164e1a 100644 --- a/spec/ruby/language/class_spec.rb +++ b/spec/ruby/language/class_spec.rb @@ -17,19 +17,6 @@ describe "The class keyword" do eval "class ClassSpecsKeywordWithoutSemicolon end" ClassSpecsKeywordWithoutSemicolon.should be_an_instance_of(Class) end - - it "can redefine a class when called from a block" do - ClassSpecs::DEFINE_CLASS.call - A.should be_an_instance_of(Class) - - Object.send(:remove_const, :A) - defined?(A).should be_nil - - ClassSpecs::DEFINE_CLASS.call - A.should be_an_instance_of(Class) - ensure - Object.send(:remove_const, :A) if defined?(::A) - end end describe "A class definition" do @@ -308,10 +295,20 @@ describe "A class definition extending an object (sclass)" do -> { class TestClass < BasicObject.new; end }.should raise_error(TypeError, error_msg) end - it "does not allow accessing the block of the original scope" do - -> { - ClassSpecs.sclass_with_block { 123 } - }.should raise_error(SyntaxError) + ruby_version_is ""..."3.0" do + it "allows accessing the block of the original scope" do + suppress_warning do + ClassSpecs.sclass_with_block { 123 }.should == 123 + end + end + end + + ruby_version_is "3.0" do + it "does not allow accessing the block of the original scope" do + -> { + ClassSpecs.sclass_with_block { 123 } + }.should raise_error(SyntaxError) + end end it "can use return to cause the enclosing method to return" do diff --git a/spec/ruby/language/class_variable_spec.rb b/spec/ruby/language/class_variable_spec.rb index a26a3fb8de..f98deaa081 100644 --- a/spec/ruby/language/class_variable_spec.rb +++ b/spec/ruby/language/class_variable_spec.rb @@ -83,32 +83,34 @@ describe 'A class variable definition' do end end -describe 'Accessing a class variable' do - it "raises a RuntimeError when accessed from the toplevel scope (not in some module or class)" do - -> { - eval "@@cvar_toplevel1" - }.should raise_error(RuntimeError, 'class variable access from toplevel') - -> { - eval "@@cvar_toplevel2 = 2" - }.should raise_error(RuntimeError, 'class variable access from toplevel') - end - - it "does not raise an error when checking if defined from the toplevel scope" do - -> { - eval "defined?(@@cvar_toplevel1)" - }.should_not raise_error - end - - it "raises a RuntimeError when a class variable is overtaken in an ancestor class" do - parent = Class.new() - subclass = Class.new(parent) - subclass.class_variable_set(:@@cvar_overtaken, :subclass) - parent.class_variable_set(:@@cvar_overtaken, :parent) - - -> { - subclass.class_variable_get(:@@cvar_overtaken) - }.should raise_error(RuntimeError, /class variable @@cvar_overtaken of .+ is overtaken by .+/) - - parent.class_variable_get(:@@cvar_overtaken).should == :parent +ruby_version_is "3.0" do + describe 'Accessing a class variable' do + it "raises a RuntimeError when accessed from the toplevel scope (not in some module or class)" do + -> { + eval "@@cvar_toplevel1" + }.should raise_error(RuntimeError, 'class variable access from toplevel') + -> { + eval "@@cvar_toplevel2 = 2" + }.should raise_error(RuntimeError, 'class variable access from toplevel') + end + + it "does not raise an error when checking if defined from the toplevel scope" do + -> { + eval "defined?(@@cvar_toplevel1)" + }.should_not raise_error + end + + it "raises a RuntimeError when a class variable is overtaken in an ancestor class" do + parent = Class.new() + subclass = Class.new(parent) + subclass.class_variable_set(:@@cvar_overtaken, :subclass) + parent.class_variable_set(:@@cvar_overtaken, :parent) + + -> { + subclass.class_variable_get(:@@cvar_overtaken) + }.should raise_error(RuntimeError, /class variable @@cvar_overtaken of .+ is overtaken by .+/) + + parent.class_variable_get(:@@cvar_overtaken).should == :parent + end end end diff --git a/spec/ruby/language/comment_spec.rb b/spec/ruby/language/comment_spec.rb index dd788e681c..690e844dc0 100644 --- a/spec/ruby/language/comment_spec.rb +++ b/spec/ruby/language/comment_spec.rb @@ -1,13 +1,15 @@ require_relative '../spec_helper' describe "The comment" do - it "can be placed between fluent dot now" do - code = <<~CODE - 10 - # some comment - .to_s - CODE + ruby_version_is "2.7" do + it "can be placed between fluent dot now" do + code = <<~CODE + 10 + # some comment + .to_s + CODE - eval(code).should == '10' + eval(code).should == '10' + end end end diff --git a/spec/ruby/language/constants_spec.rb b/spec/ruby/language/constants_spec.rb index 08c534487e..03f1272401 100644 --- a/spec/ruby/language/constants_spec.rb +++ b/spec/ruby/language/constants_spec.rb @@ -135,34 +135,18 @@ describe "Literal (A::X) constant resolution" do ConstantSpecs::ClassB::CS_CONST109.should == :const109_2 end - ruby_version_is "3.2" do - it "evaluates left-to-right" do - mod = Module.new + it "evaluates the right hand side before evaluating a constant path" do + mod = Module.new - mod.module_eval <<-EOC - order = [] - ConstantSpecsRHS = Module.new - (order << :lhs; ConstantSpecsRHS)::B = (order << :rhs) - EOC + mod.module_eval <<-EOC + ConstantSpecsRHS::B = begin + module ConstantSpecsRHS; end - mod::ConstantSpecsRHS::B.should == [:lhs, :rhs] - end - end - - ruby_version_is ""..."3.2" do - it "evaluates the right hand side before evaluating a constant path" do - mod = Module.new - - mod.module_eval <<-EOC - ConstantSpecsRHS::B = begin - module ConstantSpecsRHS; end - - "hello" - end - EOC + "hello" + end + EOC - mod::ConstantSpecsRHS::B.should == 'hello' - end + mod::ConstantSpecsRHS::B.should == 'hello' end end @@ -170,32 +154,34 @@ describe "Literal (A::X) constant resolution" do -> { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError) end - it "uses the module or class #name to craft the error message" do - mod = Module.new do - def self.name - "ModuleName" - end + ruby_version_is "3.0" do + it "uses the module or class #name to craft the error message" do + mod = Module.new do + def self.name + "ModuleName" + end - def self.inspect - "<unusable info>" + def self.inspect + "<unusable info>" + end end + + -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant ModuleName::DOES_NOT_EXIST/) end - -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant ModuleName::DOES_NOT_EXIST/) - end + it "uses the module or class #inspect to craft the error message if they are anonymous" do + mod = Module.new do + def self.name + nil + end - it "uses the module or class #inspect to craft the error message if they are anonymous" do - mod = Module.new do - def self.name - nil + def self.inspect + "<unusable info>" + end end - def self.inspect - "<unusable info>" - end + -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant <unusable info>::DOES_NOT_EXIST/) end - - -> { mod::DOES_NOT_EXIST }.should raise_error(NameError, /uninitialized constant <unusable info>::DOES_NOT_EXIST/) end it "sends #const_missing to the original class or module scope" do @@ -732,17 +718,3 @@ describe 'Allowed characters' do eval("mod::ἍBB").should == 1 end end - -describe 'Assignment' do - context 'dynamic assignment' do - it 'raises SyntaxError' do - -> do - eval <<-CODE - def test - B = 1 - end - CODE - end.should raise_error(SyntaxError, /dynamic constant assignment/) - end - end -end diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb index c8531343c0..6b0be19d90 100644 --- a/spec/ruby/language/def_spec.rb +++ b/spec/ruby/language/def_spec.rb @@ -197,15 +197,32 @@ describe "An instance method with a default argument" do foo(2,3,3).should == [2,3,[3]] end - it "raises a SyntaxError when there is an existing method with the same name as the local variable" do - def bar - 1 + ruby_version_is ''...'2.7' do + it "warns and uses a nil value when there is an existing local method with same name" do + def bar + 1 + end + -> { + eval "def foo(bar = bar) + bar + end" + }.should complain(/circular argument reference/) + foo.should == nil + foo(2).should == 2 + end + end + + ruby_version_is '2.7' do + it "raises a syntaxError an existing method with the same name as the local variable" do + def bar + 1 + end + -> { + eval "def foo(bar = bar) + bar + end" + }.should raise_error(SyntaxError) end - -> { - eval "def foo(bar = bar) - bar - end" - }.should raise_error(SyntaxError) end it "calls a method with the same name as the local when explicitly using ()" do diff --git a/spec/ruby/language/defined_spec.rb b/spec/ruby/language/defined_spec.rb index b84fe9394a..38345c3727 100644 --- a/spec/ruby/language/defined_spec.rb +++ b/spec/ruby/language/defined_spec.rb @@ -48,20 +48,6 @@ describe "The defined? keyword for literals" do end describe "The defined? keyword when called with a method name" do - before :each do - ScratchPad.clear - end - - it "does not call the method" do - defined?(DefinedSpecs.side_effects).should == "method" - ScratchPad.recorded.should != :defined_specs_side_effects - end - - it "does not execute the arguments" do - defined?(DefinedSpecs.any_args(DefinedSpecs.side_effects)).should == "method" - ScratchPad.recorded.should != :defined_specs_side_effects - end - describe "without a receiver" do it "returns 'method' if the method is defined" do ret = defined?(puts) @@ -180,32 +166,6 @@ describe "The defined? keyword when called with a method name" do ScratchPad.recorded.should == :defined_specs_fixnum_method end end - - describe "having a throw in the receiver" do - it "escapes defined? and performs the throw semantics as normal" do - defined_returned = false - catch(:out) { - # NOTE: defined? behaves differently if it is called in a void context, see below - defined?(throw(:out, 42).foo).should == :unreachable - defined_returned = true - }.should == 42 - defined_returned.should == false - end - end - - describe "in a void context" do - it "does not execute the receiver" do - ScratchPad.record :not_executed - defined?(DefinedSpecs.side_effects / 2) - ScratchPad.recorded.should == :not_executed - end - - it "warns about the void context when parsing it" do - -> { - eval "defined?(DefinedSpecs.side_effects / 2); 42" - }.should complain(/warning: possibly useless use of defined\? in void context/, verbose: true) - end - end end describe "The defined? keyword for an expression" do diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb index 020787aff6..8e4183cbcc 100644 --- a/spec/ruby/language/delegation_spec.rb +++ b/spec/ruby/language/delegation_spec.rb @@ -1,63 +1,68 @@ require_relative '../spec_helper' require_relative 'fixtures/delegation' -describe "delegation with def(...)" do - it "delegates rest and kwargs" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate(...) - target(...) - end - RUBY - - a.new.delegate(1, b: 2).should == [[1], {b: 2}] - end - - it "delegates block" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate_block(...) - target_block(...) - end - RUBY +ruby_version_is "2.7" do + describe "delegation with def(...)" do + it "delegates rest and kwargs" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(...) + target(...) + end + RUBY - a.new.delegate_block(1, b: 2) { |x| x }.should == [{b: 2}, [1]] - end + a.new.delegate(1, b: 2).should == [[1], {b: 2}] + end - it "parses as open endless Range when brackets are omitted" do - a = Class.new(DelegationSpecs::Target) - suppress_warning do + it "delegates block" do + a = Class.new(DelegationSpecs::Target) a.class_eval(<<-RUBY) - def delegate(...) - target ... + def delegate_block(...) + target_block(...) end - RUBY - end + RUBY + + a.new.delegate_block(1, b: 2) { |x| x }.should == [{b: 2}, [1]] + end - a.new.delegate(1, b: 2).should == Range.new([[], {}], nil, true) + it "parses as open endless Range when brackets are omitted" do + a = Class.new(DelegationSpecs::Target) + suppress_warning do + a.class_eval(<<-RUBY) + def delegate(...) + target ... + end + RUBY + end + + a.new.delegate(1, b: 2).should == Range.new([[], {}], nil, true) + end end end -describe "delegation with def(x, ...)" do - it "delegates rest and kwargs" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate(x, ...) - target(...) - end - RUBY +ruby_version_is "2.7.3" do + describe "delegation with def(x, ...)" do + it "delegates rest and kwargs" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(x, ...) + target(...) + end + RUBY - a.new.delegate(0, 1, b: 2).should == [[1], {b: 2}] - end + a.new.delegate(0, 1, b: 2).should == [[1], {b: 2}] + end + + it "delegates block" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate_block(x, ...) + target_block(...) + end + RUBY - it "delegates block" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate_block(x, ...) - target_block(...) - end - RUBY + a.new.delegate_block(0, 1, b: 2) { |x| x }.should == [{b: 2}, [1]] + end - a.new.delegate_block(0, 1, b: 2) { |x| x }.should == [{b: 2}, [1]] end end diff --git a/spec/ruby/language/file_spec.rb b/spec/ruby/language/file_spec.rb index 59563d9642..729dee1008 100644 --- a/spec/ruby/language/file_spec.rb +++ b/spec/ruby/language/file_spec.rb @@ -7,23 +7,23 @@ describe "The __FILE__ pseudo-variable" do -> { eval("__FILE__ = 1") }.should raise_error(SyntaxError) end - ruby_version_is ""..."3.3" do - it "equals (eval) inside an eval" do - eval("__FILE__").should == "(eval)" - end + it "equals (eval) inside an eval" do + eval("__FILE__").should == "(eval)" end +end - ruby_version_is "3.3" do - it "equals (eval at __FILE__:__LINE__) inside an eval" do - eval("__FILE__").should == "(eval at #{__FILE__}:#{__LINE__})" - end - end +describe "The __FILE__ pseudo-variable" do + it_behaves_like :language___FILE__, :require, CodeLoadingSpecs::Method.new end -describe "The __FILE__ pseudo-variable with require" do +describe "The __FILE__ pseudo-variable" do it_behaves_like :language___FILE__, :require, Kernel end -describe "The __FILE__ pseudo-variable with load" do +describe "The __FILE__ pseudo-variable" do + it_behaves_like :language___FILE__, :load, CodeLoadingSpecs::Method.new +end + +describe "The __FILE__ pseudo-variable" do it_behaves_like :language___FILE__, :load, Kernel end diff --git a/spec/ruby/language/fixtures/defined.rb b/spec/ruby/language/fixtures/defined.rb index a9552619bf..8b6004c19f 100644 --- a/spec/ruby/language/fixtures/defined.rb +++ b/spec/ruby/language/fixtures/defined.rb @@ -19,9 +19,6 @@ module DefinedSpecs DefinedSpecs end - def self.any_args(*) - end - class Basic A = 42 diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb Binary files differindex f72a32e879..d0558a2251 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb index cccc5969bd..a4d655ad02 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -p "abc".equal?("abc") +p "abc".object_id == "abc".object_id diff --git a/spec/ruby/language/fixtures/super.rb b/spec/ruby/language/fixtures/super.rb index 94a2a91be0..218f1e4970 100644 --- a/spec/ruby/language/fixtures/super.rb +++ b/spec/ruby/language/fixtures/super.rb @@ -556,20 +556,6 @@ module SuperSpecs end end - module ZSuperInBlock - class A - def m(arg:) - arg - end - end - - class B < A - def m(arg:) - proc { super }.call - end - end - end - module Keywords class Arguments def foo(**args) diff --git a/spec/ruby/language/fixtures/variables.rb b/spec/ruby/language/fixtures/variables.rb index 527caa7a78..07265dbb2b 100644 --- a/spec/ruby/language/fixtures/variables.rb +++ b/spec/ruby/language/fixtures/variables.rb @@ -82,76 +82,4 @@ module VariablesSpecs def self.false false end - - class EvalOrder - attr_reader :order - - def initialize - @order = [] - end - - def reset - @order = [] - end - - def foo - self << "foo" - FooClass.new(self) - end - - def bar - self << "bar" - BarClass.new(self) - end - - def a - self << "a" - end - - def b - self << "b" - end - - def node - self << "node" - - node = Node.new - node.left = Node.new - node.left.right = Node.new - - node - end - - def <<(value) - order << value - end - - class FooClass - attr_reader :evaluator - - def initialize(evaluator) - @evaluator = evaluator - end - - def []=(_index, _value) - evaluator << "foo[]=" - end - end - - class BarClass - attr_reader :evaluator - - def initialize(evaluator) - @evaluator = evaluator - end - - def baz=(_value) - evaluator << "bar.baz=" - end - end - - class Node - attr_accessor :left, :right - end - end end diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb index 6ac382c42c..2f8b97199a 100644 --- a/spec/ruby/language/hash_spec.rb +++ b/spec/ruby/language/hash_spec.rb @@ -127,24 +127,11 @@ describe "Hash literal" do {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4} end - it "expands an '**{}' or '**obj' element with the last key/value pair taking precedence" do + it "expands an '**{}' element with the last key/value pair taking precedence" do -> { @h = eval "{a: 1, **{a: 2, b: 3, c: 1}, c: 3}" }.should complain(/key :a is duplicated|duplicated key/) @h.should == {a: 2, b: 3, c: 3} - - -> { - h = {a: 2, b: 3, c: 1} - @h = eval "{a: 1, **h, c: 3}" - }.should_not complain - @h.should == {a: 2, b: 3, c: 3} - end - - it "expands an '**{}' and warns when finding an additional duplicate key afterwards" do - -> { - @h = eval "{d: 1, **{a: 2, b: 3, c: 1}, c: 3}" - }.should complain(/key :c is duplicated|duplicated key/) - @h.should == {a: 2, b: 3, c: 3, d: 1} end it "merges multiple nested '**obj' in Hash literals" do @@ -161,9 +148,18 @@ describe "Hash literal" do {a: 1, **obj, c: 3}.should == {a:1, b: 2, c: 3, d: 4} end - it "allows splatted elements keys that are not symbols" do - h = {1 => 2, b: 3} - {a: 1, **h}.should == {a: 1, 1 => 2, b: 3} + ruby_version_is ""..."2.7" do + it "raises a TypeError if any splatted elements keys are not symbols" do + h = {1 => 2, b: 3} + -> { {a: 1, **h} }.should raise_error(TypeError) + end + end + + ruby_version_is "2.7" do + it "allows splatted elements keys that are not symbols" do + h = {1 => 2, b: 3} + {a: 1, **h}.should == {a: 1, 1 => 2, b: 3} + end end it "raises a TypeError if #to_hash does not return a Hash" do @@ -190,22 +186,6 @@ describe "Hash literal" do utf8_hash.keys.first.should == usascii_hash.keys.first usascii_hash.keys.first.encoding.should == Encoding::US_ASCII end - - it "raises an EncodingError at parse time when Symbol key with invalid bytes" do - ScratchPad.record [] - -> { - eval 'ScratchPad << 1; {:"\xC3" => 1}' - }.should raise_error(EncodingError, 'invalid symbol in encoding UTF-8 :"\xC3"') - ScratchPad.recorded.should == [] - end - - it "raises an EncodingError at parse time when Symbol key with invalid bytes and 'key: value' syntax used" do - ScratchPad.record [] - -> { - eval 'ScratchPad << 1; {"\xC3": 1}' - }.should raise_error(EncodingError, 'invalid symbol in encoding UTF-8 :"\xC3"') - ScratchPad.recorded.should == [] - end end describe "The ** operator" do @@ -220,8 +200,8 @@ describe "The ** operator" do h.should == { one: 1, two: 2 } end - ruby_bug "#20012", ""..."3.3" do - it "makes a copy when calling a method taking a positional Hash" do + ruby_version_is ""..."3.0" do + it "makes a caller-side copy when calling a method taking a positional Hash" do def m(h) h.delete(:one); h end @@ -233,6 +213,19 @@ describe "The ** operator" do end end + ruby_version_is "3.0" do + it "does not copy when calling a method taking a positional Hash" do + def m(h) + h.delete(:one); h + end + + h = { one: 1, two: 2 } + m(**h).should == { two: 2 } + m(**h).should.equal?(h) + h.should == { two: 2 } + end + end + ruby_version_is "3.1" do describe "hash with omitted value" do it "accepts short notation 'key' for 'key: value' syntax" do diff --git a/spec/ruby/language/heredoc_spec.rb b/spec/ruby/language/heredoc_spec.rb index b3b160fd11..61a27ad8e0 100644 --- a/spec/ruby/language/heredoc_spec.rb +++ b/spec/ruby/language/heredoc_spec.rb @@ -59,10 +59,20 @@ HERE s.encoding.should == Encoding::US_ASCII end - it 'raises SyntaxError if quoted HEREDOC identifier is ending not on same line' do - -> { - eval %{<<"HERE\n"\nraises syntax error\nHERE} - }.should raise_error(SyntaxError) + ruby_version_is "2.7" do + it 'raises SyntaxError if quoted HEREDOC identifier is ending not on same line' do + -> { + eval %{<<"HERE\n"\nraises syntax error\nHERE} + }.should raise_error(SyntaxError) + end + end + + ruby_version_is ""..."2.7" do + it 'prints a warning if quoted HEREDOC identifier is ending not on same line' do + -> { + eval %{<<"HERE\n"\nit warns\nHERE} + }.should complain(/here document identifier ends with a newline/) + end end it "allows HEREDOC with <<~'identifier', allowing to indent identifier and content" do diff --git a/spec/ruby/language/if_spec.rb b/spec/ruby/language/if_spec.rb index a5da696000..d1d95c1607 100644 --- a/spec/ruby/language/if_spec.rb +++ b/spec/ruby/language/if_spec.rb @@ -306,49 +306,6 @@ describe "The if expression" do ScratchPad.recorded.should == [4, 5, 4, 5] end end - - describe "when a branch syntactically does not return a value" do - it "raises SyntaxError if both do not return a value" do - -> { - eval <<~RUBY - def m - a = if rand - return - else - return - end - a - end - RUBY - }.should raise_error(SyntaxError, /void value expression/) - end - - it "does not raise SyntaxError if one branch returns a value" do - eval(<<~RUBY).should == 1 - def m - a = if false # using false to make it clear that's not checked for - 42 - else - return 1 - end - a - end - m - RUBY - - eval(<<~RUBY).should == 1 - def m - a = if true # using true to make it clear that's not checked for - return 1 - else - 42 - end - a - end - m - RUBY - end - end end describe "The postfix if form" do diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb deleted file mode 100644 index ffb5b1fab0..0000000000 --- a/spec/ruby/language/keyword_arguments_spec.rb +++ /dev/null @@ -1,398 +0,0 @@ -require_relative '../spec_helper' - -describe "Keyword arguments" do - def target(*args, **kwargs) - [args, kwargs] - end - - it "are separated from positional arguments" do - def m(*args, **kwargs) - [args, kwargs] - end - - empty = {} - m(**empty).should == [[], {}] - m(empty).should == [[{}], {}] - - m(a: 1).should == [[], {a: 1}] - m({a: 1}).should == [[{a: 1}], {}] - end - - it "when the receiving method has not keyword parameters it treats kwargs as positional" do - def m(*a) - a - end - - m(a: 1).should == [{a: 1}] - m({a: 1}).should == [{a: 1}] - end - - context "empty kwargs are treated as if they were not passed" do - it "when calling a method" do - def m(*a) - a - end - - empty = {} - m(**empty).should == [] - m(empty).should == [{}] - end - - it "when yielding to a block" do - def y(*args, **kwargs) - yield(*args, **kwargs) - end - - empty = {} - y(**empty) { |*a| a }.should == [] - y(empty) { |*a| a }.should == [{}] - end - end - - it "extra keywords are not allowed without **kwrest" do - def m(*a, kw:) - a - end - - m(kw: 1).should == [] - -> { m(kw: 1, kw2: 2) }.should raise_error(ArgumentError, 'unknown keyword: :kw2') - -> { m(kw: 1, true => false) }.should raise_error(ArgumentError, 'unknown keyword: true') - -> { m(kw: 1, a: 1, b: 2, c: 3) }.should raise_error(ArgumentError, 'unknown keywords: :a, :b, :c') - end - - it "raises ArgumentError exception when required keyword argument is not passed" do - def m(a:, b:, c:) - [a, b, c] - end - - -> { m(a: 1, b: 2) }.should raise_error(ArgumentError, /missing keyword: :c/) - -> { m() }.should raise_error(ArgumentError, /missing keywords: :a, :b, :c/) - end - - it "raises ArgumentError for missing keyword arguments even if there are extra ones" do - def m(a:) - a - end - - -> { m(b: 1) }.should raise_error(ArgumentError, /missing keyword: :a/) - end - - it "handle * and ** at the same call site" do - def m(*a) - a - end - - m(*[], **{}).should == [] - m(*[], 42, **{}).should == [42] - end - - context "**" do - ruby_version_is "3.3" do - it "copies a non-empty Hash for a method taking (*args)" do - def m(*args) - args[0] - end - - h = {a: 1} - m(**h).should_not.equal?(h) - h.should == {a: 1} - end - end - - it "copies the given Hash for a method taking (**kwargs)" do - def m(**kw) - kw - end - - empty = {} - m(**empty).should == empty - m(**empty).should_not.equal?(empty) - - h = {a: 1} - m(**h).should == h - m(**h).should_not.equal?(h) - end - end - - context "delegation" do - it "works with (*args, **kwargs)" do - def m(*args, **kwargs) - target(*args, **kwargs) - end - - empty = {} - m(**empty).should == [[], {}] - m(empty).should == [[{}], {}] - - m(a: 1).should == [[], {a: 1}] - m({a: 1}).should == [[{a: 1}], {}] - end - - it "works with proc { |*args, **kwargs| }" do - m = proc do |*args, **kwargs| - target(*args, **kwargs) - end - - empty = {} - m.(**empty).should == [[], {}] - m.(empty).should == [[{}], {}] - - m.(a: 1).should == [[], {a: 1}] - m.({a: 1}).should == [[{a: 1}], {}] - - # no autosplatting for |*args, **kwargs| - m.([1, 2]).should == [[[1, 2]], {}] - end - - it "works with -> (*args, **kwargs) {}" do - m = -> *args, **kwargs do - target(*args, **kwargs) - end - - empty = {} - m.(**empty).should == [[], {}] - m.(empty).should == [[{}], {}] - - m.(a: 1).should == [[], {a: 1}] - m.({a: 1}).should == [[{a: 1}], {}] - end - - it "works with (...)" do - instance_eval <<~DEF - def m(...) - target(...) - end - DEF - - empty = {} - m(**empty).should == [[], {}] - m(empty).should == [[{}], {}] - - m(a: 1).should == [[], {a: 1}] - m({a: 1}).should == [[{a: 1}], {}] - end - - it "works with call(*ruby2_keyword_args)" do - class << self - ruby2_keywords def m(*args) - target(*args) - end - end - - empty = {} - m(**empty).should == [[], {}] - Hash.ruby2_keywords_hash?(empty).should == false - m(empty).should == [[{}], {}] - Hash.ruby2_keywords_hash?(empty).should == false - - m(a: 1).should == [[], {a: 1}] - m({a: 1}).should == [[{a: 1}], {}] - - kw = {a: 1} - - m(**kw).should == [[], {a: 1}] - m(**kw)[1].should == kw - m(**kw)[1].should_not.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false - - m(kw).should == [[{a: 1}], {}] - m(kw)[0][0].should.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - end - - it "works with super(*ruby2_keyword_args)" do - parent = Class.new do - def m(*args, **kwargs) - [args, kwargs] - end - end - - child = Class.new(parent) do - ruby2_keywords def m(*args) - super(*args) - end - end - - obj = child.new - - empty = {} - obj.m(**empty).should == [[], {}] - Hash.ruby2_keywords_hash?(empty).should == false - obj.m(empty).should == [[{}], {}] - Hash.ruby2_keywords_hash?(empty).should == false - - obj.m(a: 1).should == [[], {a: 1}] - obj.m({a: 1}).should == [[{a: 1}], {}] - - kw = {a: 1} - - obj.m(**kw).should == [[], {a: 1}] - obj.m(**kw)[1].should == kw - obj.m(**kw)[1].should_not.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false - - obj.m(kw).should == [[{a: 1}], {}] - obj.m(kw)[0][0].should.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - end - - it "works with zsuper" do - parent = Class.new do - def m(*args, **kwargs) - [args, kwargs] - end - end - - child = Class.new(parent) do - ruby2_keywords def m(*args) - super - end - end - - obj = child.new - - empty = {} - obj.m(**empty).should == [[], {}] - Hash.ruby2_keywords_hash?(empty).should == false - obj.m(empty).should == [[{}], {}] - Hash.ruby2_keywords_hash?(empty).should == false - - obj.m(a: 1).should == [[], {a: 1}] - obj.m({a: 1}).should == [[{a: 1}], {}] - - kw = {a: 1} - - obj.m(**kw).should == [[], {a: 1}] - obj.m(**kw)[1].should == kw - obj.m(**kw)[1].should_not.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false - - obj.m(kw).should == [[{a: 1}], {}] - obj.m(kw)[0][0].should.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - end - - it "works with yield(*ruby2_keyword_args)" do - class << self - def y(args) - yield(*args) - end - - ruby2_keywords def m(*outer_args) - y(outer_args, &-> *args, **kwargs { target(*args, **kwargs) }) - end - end - - empty = {} - m(**empty).should == [[], {}] - Hash.ruby2_keywords_hash?(empty).should == false - m(empty).should == [[{}], {}] - Hash.ruby2_keywords_hash?(empty).should == false - - m(a: 1).should == [[], {a: 1}] - m({a: 1}).should == [[{a: 1}], {}] - - kw = {a: 1} - - m(**kw).should == [[], {a: 1}] - m(**kw)[1].should == kw - m(**kw)[1].should_not.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false - - m(kw).should == [[{a: 1}], {}] - m(kw)[0][0].should.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - end - - it "does not work with (*args)" do - class << self - def m(*args) - target(*args) - end - end - - empty = {} - m(**empty).should == [[], {}] - m(empty).should == [[{}], {}] - - m(a: 1).should == [[{a: 1}], {}] - m({a: 1}).should == [[{a: 1}], {}] - end - - ruby_version_is "3.1" do - describe "omitted values" do - it "accepts short notation 'key' for 'key: value' syntax" do - def m(a:, b:) - [a, b] - end - - a = 1 - b = 2 - - eval('m(a:, b:).should == [1, 2]') - end - end - end - - ruby_version_is "3.2" do - it "does not work with call(*ruby2_keyword_args) with missing ruby2_keywords in between" do - class << self - def n(*args) # Note the missing ruby2_keywords here - target(*args) - end - - ruby2_keywords def m(*args) - n(*args) - end - end - - empty = {} - m(**empty).should == [[], {}] - m(empty).should == [[{}], {}] - - m(a: 1).should == [[{a: 1}], {}] - m({a: 1}).should == [[{a: 1}], {}] - end - end - - ruby_version_is ""..."3.2" do - # https://bugs.ruby-lang.org/issues/18625 - it "works with call(*ruby2_keyword_args) with missing ruby2_keywords in between due to CRuby bug #18625" do - class << self - def n(*args) # Note the missing ruby2_keywords here - target(*args) - end - - ruby2_keywords def m(*args) - n(*args) - end - end - - empty = {} - m(**empty).should == [[], {}] - Hash.ruby2_keywords_hash?(empty).should == false - m(empty).should == [[{}], {}] - Hash.ruby2_keywords_hash?(empty).should == false - - m(a: 1).should == [[], {a: 1}] - m({a: 1}).should == [[{a: 1}], {}] - - kw = {a: 1} - - m(**kw).should == [[], {a: 1}] - m(**kw)[1].should == kw - m(**kw)[1].should_not.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false - - m(kw).should == [[{a: 1}], {}] - m(kw)[0][0].should.equal?(kw) - Hash.ruby2_keywords_hash?(kw).should == false - end - end - end -end diff --git a/spec/ruby/language/lambda_spec.rb b/spec/ruby/language/lambda_spec.rb index 3ab3569ebe..6393fb5c47 100644 --- a/spec/ruby/language/lambda_spec.rb +++ b/spec/ruby/language/lambda_spec.rb @@ -177,16 +177,34 @@ describe "A lambda literal -> () { }" do result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12] end - evaluate <<-ruby do - @a = -> (*, **k) { k } - ruby + ruby_version_is ''...'3.0' do + evaluate <<-ruby do + @a = -> (*, **k) { k } + ruby + + @a.().should == {} + @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + + suppress_keyword_warning do + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + @a.(h).should == {a: 1} + end + end + end - @a.().should == {} - @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + ruby_version_is '3.0' do + evaluate <<-ruby do + @a = -> (*, **k) { k } + ruby - h = mock("keyword splat") - h.should_not_receive(:to_hash) - @a.(h).should == {} + @a.().should == {} + @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + + h = mock("keyword splat") + h.should_not_receive(:to_hash) + @a.(h).should == {} + end end evaluate <<-ruby do @@ -263,18 +281,38 @@ describe "A lambda literal -> () { }" do end describe "with circular optional argument reference" do - it "raises a SyntaxError if using an existing local with the same name as the argument" do - a = 1 - -> { - @proc = eval "-> (a=a) { a }" - }.should raise_error(SyntaxError) + ruby_version_is ''...'2.7' do + it "warns and uses a nil value when there is an existing local variable with same name" do + a = 1 + -> { + @proc = eval "-> (a=a) { a }" + }.should complain(/circular argument reference/) + @proc.call.should == nil + end + + it "warns and uses a nil value when there is an existing method with same name" do + def a; 1; end + -> { + @proc = eval "-> (a=a) { a }" + }.should complain(/circular argument reference/) + @proc.call.should == nil + 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 "-> (a=a) { a }" - }.should raise_error(SyntaxError) + ruby_version_is '2.7' do + it "raises a SyntaxError if using an existing local with the same name as the argument" do + a = 1 + -> { + @proc = eval "-> (a=a) { a }" + }.should raise_error(SyntaxError) + 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 "-> (a=a) { a }" + }.should raise_error(SyntaxError) + end end it "calls an existing method with the same name as the argument if explicitly using ()" do @@ -322,12 +360,26 @@ describe "A lambda expression 'lambda { ... }'" do def meth; lambda; end end - it "raises ArgumentError" do - implicit_lambda = nil - suppress_warning do + ruby_version_is ""..."2.7" do + it "can be created" do + implicit_lambda = nil -> { - meth { 1 } - }.should raise_error(ArgumentError, /tried to create Proc object without a block/) + implicit_lambda = meth { 1 } + }.should complain(/tried to create Proc object without a block/) + + implicit_lambda.lambda?.should be_true + implicit_lambda.call.should == 1 + end + end + + ruby_version_is "2.7" do + it "raises ArgumentError" do + implicit_lambda = nil + suppress_warning do + -> { + meth { 1 } + }.should raise_error(ArgumentError, /tried to create Proc object without a block/) + end end end end @@ -496,16 +548,34 @@ describe "A lambda expression 'lambda { ... }'" do result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12] end - evaluate <<-ruby do - @a = lambda { |*, **k| k } - ruby + ruby_version_is ''...'3.0' do + evaluate <<-ruby do + @a = lambda { |*, **k| k } + ruby - @a.().should == {} - @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + @a.().should == {} + @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + + suppress_keyword_warning do + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + @a.(h).should == {a: 1} + end + end + end + + ruby_version_is '3.0' do + evaluate <<-ruby do + @a = lambda { |*, **k| k } + ruby - h = mock("keyword splat") - h.should_not_receive(:to_hash) - @a.(h).should == {} + @a.().should == {} + @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + + h = mock("keyword splat") + h.should_not_receive(:to_hash) + @a.(h).should == {} + end end evaluate <<-ruby do diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index 5f42c52341..0a5bb99d0b 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -572,13 +572,6 @@ describe "A method" do 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 @@ -609,11 +602,13 @@ describe "A method" do -> { m(2) }.should raise_error(ArgumentError) end - evaluate <<-ruby do - def m(**k); k end; - ruby + ruby_version_is "2.7" do + evaluate <<-ruby do + def m(**k); k end; + ruby - m("a" => 1).should == { "a" => 1 } + m("a" => 1).should == { "a" => 1 } + end end evaluate <<-ruby do @@ -749,31 +744,68 @@ describe "A method" do end end - evaluate <<-ruby do - def m(a, b: 1) [a, b] end - ruby + ruby_version_is ""..."3.0" do + 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 + m(2).should == [2, 1] + m(1, b: 2).should == [1, 2] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, 1] + end + end - evaluate <<-ruby do - def m(a, **) a end - ruby + 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) + m(1).should == 1 + m(1, a: 2, b: 3).should == 1 + suppress_keyword_warning do + m("a" => 1, b: 2).should == {"a" => 1, b: 2} + end + 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}] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, {}] + end + end end - evaluate <<-ruby do - def m(a, **k) [a, k] end - ruby + ruby_version_is "3.0" do + evaluate <<-ruby do + def m(a, b: 1) [a, b] 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) + 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 end evaluate <<-ruby do @@ -884,32 +916,72 @@ describe "A method" do result.should == [[1, 2, 3], 4, [5, 6], 7, [], 8] end - evaluate <<-ruby do - def m(a=1, b:) [a, b] end - ruby + ruby_version_is ""..."3.0" do + 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] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [{"a" => 1}, 2] + end + end - m(b: 2).should == [1, 2] - m(2, b: 1).should == [2, 1] - -> { m("a" => 1, b: 2) }.should raise_error(ArgumentError) + 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] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [{"a" => 1}, 2] + end + end end - evaluate <<-ruby do - def m(a=1, b: 2) [a, b] end - ruby + ruby_version_is "3.0" do + evaluate <<-ruby do + def m(a=1, b:) [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) + 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 end - evaluate <<-ruby do - def m(a=1, **) a end - ruby + ruby_version_is ""..."2.7" do + 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 + m().should == 1 + m(2, a: 1, b: 0).should == 2 + m("a" => 1, a: 2).should == {"a" => 1} + end + end + + ruby_version_is "2.7" do + 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 end evaluate <<-ruby do @@ -949,6 +1021,449 @@ describe "A method" do m(1, 2, 3).should == [[1, 2], 3] end + ruby_version_is ""..."2.7" do + evaluate <<-ruby do + def m(*, a:) a end + ruby + + m(a: 1).should == 1 + m(1, 2, a: 3).should == 3 + suppress_keyword_warning do + m("a" => 1, a: 2).should == 2 + end + end + + evaluate <<-ruby do + def m(*a, b:) [a, b] end + ruby + + m(b: 1).should == [[], 1] + m(1, 2, b: 3).should == [[1, 2], 3] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [[{"a" => 1}], 2] + end + end + + evaluate <<-ruby do + def m(*, a: 1) a end + ruby + + m().should == 1 + m(1, 2).should == 1 + m(a: 2).should == 2 + m(1, a: 2).should == 2 + suppress_keyword_warning do + m("a" => 1, a: 2).should == 2 + end + end + + evaluate <<-ruby do + def m(*a, b: 1) [a, b] end + ruby + + m().should == [[], 1] + m(1, 2, 3, b: 4).should == [[1, 2, 3], 4] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [[{"a" => 1}], 2] + end + + a = mock("splat") + a.should_not_receive(:to_ary) + m(*a).should == [[a], 1] + end + + evaluate <<-ruby do + def m(*, **) end + ruby + + m().should be_nil + m(a: 1, b: 2).should be_nil + m(1, 2, 3, a: 4, b: 5).should be_nil + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + suppress_keyword_warning do + m(h).should be_nil + end + + h = mock("keyword splat") + error = RuntimeError.new("error while converting to a hash") + h.should_receive(:to_hash).and_raise(error) + -> { m(h) }.should raise_error(error) + end + + evaluate <<-ruby do + def m(*a, **) a end + ruby + + m().should == [] + m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3] + m("a" => 1, a: 1).should == [{"a" => 1}] + m(1, **{a: 2}).should == [1] + + h = mock("keyword splat") + h.should_receive(:to_hash) + -> { m(**h) }.should raise_error(TypeError) + end + + evaluate <<-ruby do + def m(*, **k) k end + ruby + + m().should == {} + m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + m("a" => 1, a: 1).should == {a: 1} + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + m(h).should == {a: 1} + end + + evaluate <<-ruby do + def m(a = nil, **k) [a, k] end + ruby + + m().should == [nil, {}] + m("a" => 1).should == [{"a" => 1}, {}] + m(a: 1).should == [nil, {a: 1}] + m("a" => 1, a: 1).should == [{"a" => 1}, {a: 1}] + m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}] + m({a: 1}, {}).should == [{a: 1}, {}] + + h = {"a" => 1, b: 2} + m(h).should == [{"a" => 1}, {b: 2}] + h.should == {"a" => 1, b: 2} + + h = {"a" => 1} + m(h).first.should == h + + h = {} + r = m(h) + r.first.should be_nil + r.last.should == {} + + hh = {} + h = mock("keyword splat empty hash") + h.should_receive(:to_hash).and_return(hh) + r = m(h) + r.first.should be_nil + r.last.should == {} + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({"a" => 1, a: 2}) + m(h).should == [{"a" => 1}, {a: 2}] + end + + evaluate <<-ruby do + def m(*a, **k) [a, k] end + ruby + + m().should == [[], {}] + m(1).should == [[1], {}] + m(a: 1, b: 2).should == [[], {a: 1, b: 2}] + m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}] + + m("a" => 1).should == [[{"a" => 1}], {}] + m(a: 1).should == [[], {a: 1}] + m("a" => 1, a: 1).should == [[{"a" => 1}], {a: 1}] + m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}] + m({a: 1}, {}).should == [[{a: 1}], {}] + m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}] + + bo = BasicObject.new + def bo.to_a; [1, 2, 3]; end + def bo.to_hash; {:b => 2, :c => 3}; end + + m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}] + end + end + + ruby_version_is "2.7"...'3.0' do + evaluate <<-ruby do + def m(*, a:) a end + ruby + + m(a: 1).should == 1 + m(1, 2, a: 3).should == 3 + suppress_keyword_warning do + m("a" => 1, a: 2).should == 2 + end + end + + evaluate <<-ruby do + def m(*a, b:) [a, b] end + ruby + + m(b: 1).should == [[], 1] + m(1, 2, b: 3).should == [[1, 2], 3] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [[{"a" => 1}], 2] + end + end + + evaluate <<-ruby do + def m(*, a: 1) a end + ruby + + m().should == 1 + m(1, 2).should == 1 + m(a: 2).should == 2 + m(1, a: 2).should == 2 + suppress_keyword_warning do + m("a" => 1, a: 2).should == 2 + end + end + + evaluate <<-ruby do + def m(*a, b: 1) [a, b] end + ruby + + m().should == [[], 1] + m(1, 2, 3, b: 4).should == [[1, 2, 3], 4] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [[{"a" => 1}], 2] + end + + a = mock("splat") + a.should_not_receive(:to_ary) + m(*a).should == [[a], 1] + end + + evaluate <<-ruby do + def m(*a, **) a end + ruby + + m().should == [] + m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3] + m("a" => 1, a: 1).should == [] + m(1, **{a: 2}).should == [1] + + h = mock("keyword splat") + h.should_receive(:to_hash) + -> { m(**h) }.should raise_error(TypeError) + end + + evaluate <<-ruby do + def m(*, **k) k end + ruby + + m().should == {} + m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + m("a" => 1, a: 1).should == {"a" => 1, a: 1} + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + suppress_warning do + m(h).should == {a: 1} + end + end + + evaluate <<-ruby do + def m(a = nil, **k) [a, k] end + ruby + + m().should == [nil, {}] + m("a" => 1).should == [nil, {"a" => 1}] + m(a: 1).should == [nil, {a: 1}] + m("a" => 1, a: 1).should == [nil, {"a" => 1, a: 1}] + m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}] + suppress_warning do + m({a: 1}, {}).should == [{a: 1}, {}] + + h = {"a" => 1, b: 2} + m(h).should == [{"a" => 1}, {b: 2}] + h.should == {"a" => 1, b: 2} + + h = {"a" => 1} + m(h).first.should == h + + h = {} + r = m(h) + r.first.should be_nil + r.last.should == {} + + hh = {} + h = mock("keyword splat empty hash") + h.should_receive(:to_hash).and_return(hh) + r = m(h) + r.first.should be_nil + r.last.should == {} + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({"a" => 1, a: 2}) + m(h).should == [{"a" => 1}, {a: 2}] + end + end + + evaluate <<-ruby do + def m(*a, **k) [a, k] end + ruby + + m().should == [[], {}] + m(1).should == [[1], {}] + m(a: 1, b: 2).should == [[], {a: 1, b: 2}] + m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}] + + m("a" => 1).should == [[], {"a" => 1}] + m(a: 1).should == [[], {a: 1}] + m("a" => 1, a: 1).should == [[], {"a" => 1, a: 1}] + m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}] + suppress_warning do + m({a: 1}, {}).should == [[{a: 1}], {}] + end + m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}] + + bo = BasicObject.new + def bo.to_a; [1, 2, 3]; end + def bo.to_hash; {:b => 2, :c => 3}; end + + m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}] + end + + evaluate <<-ruby do + def m(*, a:) a end + ruby + + m(a: 1).should == 1 + m(1, 2, a: 3).should == 3 + suppress_keyword_warning do + m("a" => 1, a: 2).should == 2 + end + end + + evaluate <<-ruby do + def m(*a, b:) [a, b] end + ruby + + m(b: 1).should == [[], 1] + m(1, 2, b: 3).should == [[1, 2], 3] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [[{"a" => 1}], 2] + end + end + + evaluate <<-ruby do + def m(*, a: 1) a end + ruby + + m().should == 1 + m(1, 2).should == 1 + m(a: 2).should == 2 + m(1, a: 2).should == 2 + suppress_keyword_warning do + m("a" => 1, a: 2).should == 2 + end + end + + evaluate <<-ruby do + def m(*a, b: 1) [a, b] end + ruby + + m().should == [[], 1] + m(1, 2, 3, b: 4).should == [[1, 2, 3], 4] + suppress_keyword_warning do + m("a" => 1, b: 2).should == [[{"a" => 1}], 2] + end + + a = mock("splat") + a.should_not_receive(:to_ary) + m(*a).should == [[a], 1] + end + + evaluate <<-ruby do + def m(*a, **) a end + ruby + + m().should == [] + m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3] + m("a" => 1, a: 1).should == [] + m(1, **{a: 2}).should == [1] + + h = mock("keyword splat") + h.should_receive(:to_hash) + -> { m(**h) }.should raise_error(TypeError) + end + + evaluate <<-ruby do + def m(*, **k) k end + ruby + + m().should == {} + m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + m("a" => 1, a: 1).should == {"a" => 1, a: 1} + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + suppress_keyword_warning do + m(h).should == {a: 1} + end + end + + evaluate <<-ruby do + def m(a = nil, **k) [a, k] end + ruby + + m().should == [nil, {}] + m("a" => 1).should == [nil, {"a" => 1}] + m(a: 1).should == [nil, {a: 1}] + m("a" => 1, a: 1).should == [nil, {"a" => 1, a: 1}] + m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}] + suppress_keyword_warning do + m({a: 1}, {}).should == [{a: 1}, {}] + end + + h = {"a" => 1, b: 2} + suppress_keyword_warning do + m(h).should == [{"a" => 1}, {b: 2}] + end + h.should == {"a" => 1, b: 2} + + h = {"a" => 1} + m(h).first.should == h + + h = {} + suppress_keyword_warning do + m(h).should == [nil, {}] + end + + hh = {} + h = mock("keyword splat empty hash") + h.should_receive(:to_hash).and_return({a: 1}) + suppress_keyword_warning do + m(h).should == [nil, {a: 1}] + end + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({"a" => 1}) + m(h).should == [h, {}] + end + + evaluate <<-ruby do + def m(*a, **k) [a, k] end + ruby + + m().should == [[], {}] + m(1).should == [[1], {}] + m(a: 1, b: 2).should == [[], {a: 1, b: 2}] + m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}] + + m("a" => 1).should == [[], {"a" => 1}] + m(a: 1).should == [[], {a: 1}] + m("a" => 1, a: 1).should == [[], {"a" => 1, a: 1}] + m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}] + suppress_keyword_warning do + m({a: 1}, {}).should == [[{a: 1}], {}] + end + m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}] + + bo = BasicObject.new + def bo.to_a; [1, 2, 3]; end + def bo.to_hash; {:b => 2, :c => 3}; end + + m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}] + end + end + evaluate <<-ruby do def m(*, &b) b end ruby @@ -988,22 +1503,44 @@ describe "A method" do end end - evaluate <<-ruby do - def m(a:, **) a end - ruby + ruby_version_is ''...'2.7' do + 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 + m(a: 1).should == 1 + m(a: 1, b: 2).should == 1 + -> { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) + 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 raise_error(ArgumentError) + end end - evaluate <<-ruby do - def m(a:, **k) [a, k] end - ruby + ruby_version_is '2.7' do + 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 - 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}] + 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 end evaluate <<-ruby do @@ -1100,66 +1637,125 @@ describe "A method" do result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] end - evaluate <<-ruby do - def m(a, **nil); a end; - ruby + ruby_version_is "2.7" do + 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 == {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') + -> { m(a: 1) }.should raise_error(ArgumentError) + -> { m(**{a: 1}) }.should raise_error(ArgumentError) + -> { m("a" => 1) }.should raise_error(ArgumentError) + end end - evaluate <<-ruby do - def m(a, b = nil, c = nil, d, e: nil, **f) - [a, b, c, d, e, f] + ruby_version_is ''...'3.0' do + 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, {}] + + suppress_warning do + result = m(1, 2, {foo: :bar}) + result.should == [1, nil, nil, 2, nil, {foo: :bar}] end - ruby - result = m(1, 2) - result.should == [1, nil, nil, 2, nil, {}] + result = m(1, {foo: :bar}) + result.should == [1, nil, nil, {foo: :bar}, nil, {}] + end + end + + ruby_version_is '3.0' do + 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, 2, {foo: :bar}) + result.should == [1, 2, nil, {foo: :bar}, nil, {}] - result = m(1, {foo: :bar}) - result.should == [1, nil, nil, {foo: :bar}, nil, {}] + result = m(1, {foo: :bar}) + result.should == [1, nil, nil, {foo: :bar}, nil, {}] + end 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 + ruby_version_is '2.7' do + 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 == [] + h = {} + m(**h).should == [] + end 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 = {} + ruby_version_is '2.7'...'3.0' do + 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) + -> do + m(**h).should == {} + end.should complain(/warning: Passing the keyword argument as the last hash parameter is deprecated/) + end end end - context "raises ArgumentError if passing hash as keyword arguments" do - evaluate <<-ruby do - def m(a: nil); a; end - ruby + ruby_version_is ''...'3.0' do + context "assigns keyword arguments from a passed Hash without modifying it" do + evaluate <<-ruby do + def m(a: nil); a; end + ruby + + options = {a: 1}.freeze + -> do + suppress_warning do + m(options).should == 1 + end + end.should_not raise_error + options.should == {a: 1} + end + end + end + + ruby_version_is '3.0' do + 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 = {} - options = {a: 1}.freeze - -> do - m(options) - end.should raise_error(ArgumentError) + -> 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 end @@ -1193,42 +1789,14 @@ describe "A method call with a space between method name and parentheses" do 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 + context "when a single argument provided" do + it "assigns it" 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 + context "when 2+ arguments provided" do it "raises a syntax error" do -> { eval("m (1, 2)") @@ -1293,107 +1861,86 @@ describe "An array-dereference method ([])" do end end -describe "An endless method definition" do - context "without arguments" do - evaluate <<-ruby do - def m() = 42 - ruby +ruby_version_is "3.0" do + describe "An endless method definition" do + context "without arguments" do + evaluate <<-ruby do + def m() = 42 + ruby - m.should == 42 + m.should == 42 + end end - context "without parenthesis" do + context "with arguments" do evaluate <<-ruby do - def m = 42 - ruby + def m(a, b) = a + b + ruby - m.should == 42 + m(1, 4).should == 5 end end - end - context "with arguments" do - evaluate <<-ruby do - def m(a, b) = a + b - ruby + 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(1, 4).should == 5 + m(6).should == 8 + end end - end - context "with multiline body" do - evaluate <<-ruby do - def m(n) = - if n > 2 - m(n - 2) + m(n - 1) - else - 1 + context "with args forwarding" do + evaluate <<-ruby do + def mm(word, num:) + word * num end - ruby - m(6).should == 8 + def m(...) = mm(...) + mm(...) + ruby + + m("meow", num: 2).should == "meow" * 4 + end end end - context "with args forwarding" do - evaluate <<-ruby do - def mm(word, num:) - word * num + 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 - 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] + foo(1, 2, 3, key: 42).should == 42 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 + 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 - it "captures the passed ** keyword arguments" do - def foo(a, b, c, **hsh) - hsh[:key] + foo(1, 2, 3, key: 42).should == 42 end - h = { key: 42 } - foo(1, 2, 3, **h).should == 42 - end + it "captures the passed ** keyword arguments" do + def foo(a, b, c, **hsh) + hsh[:key] + end - it "does not convert a positional Hash to keyword arguments" do - def foo(a, b, c, **hsh) - hsh[:key] + h = { key: 42 } + foo(1, 2, 3, **h).should == 42 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 + it "does not convert a positional Hash to keyword arguments" do + def foo(a, b, c, **hsh) + hsh[:key] end -> { @@ -1402,14 +1949,28 @@ describe "Keyword arguments are now separated from positional arguments" do end end - context "when it's called with **" do - it "captures the passed keyword arguments" do - def foo(a, b, c, key: 1) - key + 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 - h = { key: 42 } - foo(1, 2, 3, **h).should == 42 + 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 @@ -1454,14 +2015,4 @@ ruby_version_is "3.1" do end end end - - describe "Inside 'endless' method definitions" do - it "allows method calls without parenthesis" do - eval <<-ruby - def greet(person) = "Hi, ".concat person - ruby - - greet("Homer").should == "Hi, Homer" - end - end end diff --git a/spec/ruby/language/module_spec.rb b/spec/ruby/language/module_spec.rb index fffcf9c90d..1a5fcaf46f 100644 --- a/spec/ruby/language/module_spec.rb +++ b/spec/ruby/language/module_spec.rb @@ -28,18 +28,9 @@ describe "The module keyword" do ModuleSpecs::Reopened.should be_true end - ruby_version_is '3.2' do - it "does not reopen a module included in Object" do - module IncludedModuleSpecs; Reopened = true; end - ModuleSpecs::IncludedInObject::IncludedModuleSpecs.should_not == Object::IncludedModuleSpecs - end - end - - ruby_version_is ''...'3.2' do - it "reopens a module included in Object" do - module IncludedModuleSpecs; Reopened = true; end - ModuleSpecs::IncludedInObject::IncludedModuleSpecs::Reopened.should be_true - end + it "reopens a module included in Object" do + module IncludedModuleSpecs; Reopened = true; end + ModuleSpecs::IncludedInObject::IncludedModuleSpecs::Reopened.should be_true end it "raises a TypeError if the constant is a Class" do @@ -78,10 +69,20 @@ describe "Assigning an anonymous module to a constant" do mod.name.should == "ModuleSpecs_CS1" end - it "sets the name of a module scoped by an anonymous module" do - a, b = Module.new, Module.new - a::B = b - b.name.should.end_with? '::B' + ruby_version_is ""..."3.0" do + it "does not set the name of a module scoped by an anonymous module" do + a, b = Module.new, Module.new + a::B = b + b.name.should be_nil + end + end + + ruby_version_is "3.0" do + it "sets the name of a module scoped by an anonymous module" do + a, b = Module.new, Module.new + a::B = b + b.name.should.end_with? '::B' + end end it "sets the name of contained modules when assigning a toplevel anonymous module" do diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb index 3a35cf1465..838822b2d6 100644 --- a/spec/ruby/language/numbered_parameters_spec.rb +++ b/spec/ruby/language/numbered_parameters_spec.rb @@ -1,104 +1,120 @@ require_relative '../spec_helper' -describe "Numbered parameters" do - it "provides default parameters _1, _2, ... in a block" do - -> { _1 }.call("a").should == "a" - proc { _1 }.call("a").should == "a" - lambda { _1 }.call("a").should == "a" - ["a"].map { _1 }.should == ["a"] - end - - it "assigns nil to not passed parameters" do - proc { [_1, _2] }.call("a").should == ["a", nil] - proc { [_1, _2] }.call("a", "b").should == ["a", "b"] - end +ruby_version_is "2.7" do + describe "Numbered parameters" do + it "provides default parameters _1, _2, ... in a block" do + -> { _1 }.call("a").should == "a" + proc { _1 }.call("a").should == "a" + lambda { _1 }.call("a").should == "a" + ["a"].map { _1 }.should == ["a"] + end - it "supports variables _1-_9 only for the first 9 passed parameters" do - block = proc { [_1, _2, _3, _4, _5, _6, _7, _8, _9] } - result = block.call(1, 2, 3, 4, 5, 6, 7, 8, 9) - result.should == [1, 2, 3, 4, 5, 6, 7, 8, 9] - end + it "assigns nil to not passed parameters" do + proc { [_1, _2] }.call("a").should == ["a", nil] + proc { [_1, _2] }.call("a", "b").should == ["a", "b"] + end - it "does not support more than 9 parameters" do - -> { - proc { [_10] }.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - }.should raise_error(NameError, /undefined local variable or method `_10'/) - end + it "supports variables _1-_9 only for the first 9 passed parameters" do + block = proc { [_1, _2, _3, _4, _5, _6, _7, _8, _9] } + result = block.call(1, 2, 3, 4, 5, 6, 7, 8, 9) + result.should == [1, 2, 3, 4, 5, 6, 7, 8, 9] + end - it "can not be used in both outer and nested blocks at the same time" do - -> { - eval("-> { _1; -> { _2 } }") - }.should raise_error(SyntaxError, /numbered parameter is already used in/m) - end + it "does not support more than 9 parameters" do + -> { + proc { [_10] }.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + }.should raise_error(NameError, /undefined local variable or method `_10'/) + end - it "cannot be overwritten with local variable" do - -> { - eval <<~CODE - _1 = 0 - proc { _1 }.call("a").should == 0 - CODE - }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) - end + it "can not be used in both outer and nested blocks at the same time" do + -> { + eval("-> { _1; -> { _2 } }") + }.should raise_error(SyntaxError, /numbered parameter is already used in/m) + end - it "errors when numbered parameter is overwritten with local variable" do - -> { - eval("_1 = 0") - }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) - end + ruby_version_is '2.7'...'3.0' do + it "can be overwritten with local variable" do + suppress_warning do + eval <<~CODE + _1 = 0 + proc { _1 }.call("a").should == 0 + CODE + end + end + + it "warns when numbered parameter is overwritten with local variable" do + -> { + eval("_1 = 0") + }.should complain(/warning: `_1' is reserved for numbered parameter; consider another name/) + end + end - it "raises SyntaxError when block parameters are specified explicitly" do - -> { eval("-> () { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("-> (x) { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + ruby_version_is '3.0' do + it "cannot be overwritten with local variable" do + -> { + eval <<~CODE + _1 = 0 + proc { _1 }.call("a").should == 0 + CODE + }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) + end + + it "errors when numbered parameter is overwritten with local variable" do + -> { + eval("_1 = 0") + }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) + end + end - -> { eval("proc { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("proc { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + it "raises SyntaxError when block parameters are specified explicitly" do + -> { eval("-> () { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("-> (x) { _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("lambda { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("lambda { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("proc { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("proc { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("['a'].map { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("['a'].map { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - end + -> { eval("lambda { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("lambda { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - describe "assigning to a numbered parameter" do - it "raises SyntaxError" do - -> { eval("proc { _1 = 0 }") }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) + -> { eval("['a'].map { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("['a'].map { |x| _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) end - end - - it "affects block arity" do - -> { _1 }.arity.should == 1 - -> { _2 }.arity.should == 2 - -> { _3 }.arity.should == 3 - -> { _4 }.arity.should == 4 - -> { _5 }.arity.should == 5 - -> { _6 }.arity.should == 6 - -> { _7 }.arity.should == 7 - -> { _8 }.arity.should == 8 - -> { _9 }.arity.should == 9 - - -> { _9 }.arity.should == 9 - proc { _9 }.arity.should == 9 - lambda { _9 }.arity.should == 9 - end - it "affects block parameters" do - -> { _1 }.parameters.should == [[:req, :_1]] - -> { _2 }.parameters.should == [[:req, :_1], [:req, :_2]] - - proc { _1 }.parameters.should == [[:opt, :_1]] - proc { _2 }.parameters.should == [[:opt, :_1], [:opt, :_2]] - end + describe "assigning to a numbered parameter" do + ruby_version_is '2.7'...'3.0' do + it "warns" do + -> { eval("proc { _1 = 0 }") }.should complain(/warning: `_1' is reserved for numbered parameter; consider another name/) + end + end + + ruby_version_is '3.0' do + it "raises SyntaxError" do + -> { eval("proc { _1 = 0 }") }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) + end + end + end - it "affects binding local variables" do - -> { _1; binding.local_variables }.call("a").should == [:_1] - -> { _2; binding.local_variables }.call("a", "b").should == [:_1, :_2] - end + it "affects block arity" do + -> { _1 }.arity.should == 1 + -> { _2 }.arity.should == 2 + -> { _3 }.arity.should == 3 + -> { _4 }.arity.should == 4 + -> { _5 }.arity.should == 5 + -> { _6 }.arity.should == 6 + -> { _7 }.arity.should == 7 + -> { _8 }.arity.should == 8 + -> { _9 }.arity.should == 9 + + -> { _9 }.arity.should == 9 + proc { _9 }.arity.should == 9 + lambda { _9 }.arity.should == 9 + end - it "does not work in methods" do - obj = Object.new - def obj.foo; _1 end + it "does not work in methods" do + obj = Object.new + def obj.foo; _1 end - -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/) + -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/) + end end end diff --git a/spec/ruby/language/numbers_spec.rb b/spec/ruby/language/numbers_spec.rb index a8e023efb6..2d8e19c40a 100644 --- a/spec/ruby/language/numbers_spec.rb +++ b/spec/ruby/language/numbers_spec.rb @@ -53,7 +53,7 @@ describe "A number literal" do eval('0.0174532925199432957r').should == Rational(174532925199432957, 10000000000000000000) end - it "can be a bignum literal with trailing 'r' to represent a Rational" do + it "can be an bignum literal with trailing 'r' to represent a Rational" do eval('1111111111111111111111111111111111111111111111r').should == Rational(1111111111111111111111111111111111111111111111, 1) eval('-1111111111111111111111111111111111111111111111r').should == Rational(-1111111111111111111111111111111111111111111111, 1) end diff --git a/spec/ruby/language/optional_assignments_spec.rb b/spec/ruby/language/optional_assignments_spec.rb index 02461655d6..217dcab74b 100644 --- a/spec/ruby/language/optional_assignments_spec.rb +++ b/spec/ruby/language/optional_assignments_spec.rb @@ -300,44 +300,6 @@ describe 'Optional variable assignments' do (@b[:k] ||= 12).should == 12 end - it 'correctly handles a splatted argument for the index' do - (@b[*[:k]] ||= 12).should == 12 - end - - it "evaluates the index precisely once" do - ary = [:x, :y] - @a[:x] = 15 - @a[ary.pop] ||= 25 - ary.should == [:x] - @a.should == { x: 15, y: 25 } - end - - it "evaluates the index arguments in the correct order" do - ary = Class.new(Array) do - def [](x, y) - super(x + 3 * y) - end - - def []=(x, y, value) - super(x + 3 * y, value) - end - end.new - ary[0, 0] = 1 - ary[1, 0] = 1 - ary[2, 0] = nil - ary[3, 0] = 1 - ary[4, 0] = 1 - ary[5, 0] = 1 - ary[6, 0] = nil - - foo = [0, 2] - - ary[foo.pop, foo.pop] ||= 2 - - ary[2, 0].should == 2 - ary[6, 0].should == nil - end - it 'returns the assigned value, not the result of the []= method with +=' do @b[:k] = 17 (@b[:k] += 12).should == 29 diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb index 050a8a052d..e4abae9412 100644 --- a/spec/ruby/language/pattern_matching_spec.rb +++ b/spec/ruby/language/pattern_matching_spec.rb @@ -1,370 +1,196 @@ require_relative '../spec_helper' -describe "Pattern matching" do - # TODO: Remove excessive eval calls when Ruby 3 is the minimum version. - # It is best to keep the eval's longer if other Ruby impls cannot parse pattern matching yet. +ruby_version_is "2.7" do + describe "Pattern matching" do + # TODO: Remove excessive eval calls when support of previous version + # Ruby 2.6 will be dropped - before :each do - ScratchPad.record [] - end - - describe "can be standalone assoc operator that" do - it "deconstructs value" do - suppress_warning do - eval(<<-RUBY).should == [0, 1] - [0, 1] => [a, b] - [a, b] - RUBY - end + before :each do + ScratchPad.record [] end - it "deconstructs value and properly scopes variables" do - suppress_warning do - eval(<<-RUBY).should == [0, nil] - a = nil - eval(<<-PATTERN) + ruby_version_is "3.0" do + it "can be standalone assoc operator that deconstructs value" do + suppress_warning do + eval(<<-RUBY).should == [0, 1] [0, 1] => [a, b] - PATTERN - [a, defined?(b)] - RUBY - end - end - end - - describe "find pattern" do - it "captures preceding elements to the pattern" do - eval(<<~RUBY).should == [0, 1] - case [0, 1, 2, 3] - in [*pre, 2, 3] - pre - else - false - end - RUBY - end - - it "captures following elements to the pattern" do - eval(<<~RUBY).should == [2, 3] - case [0, 1, 2, 3] - in [0, 1, *post] - post - else - false + [a, b] + RUBY end - RUBY - end - - it "captures both preceding and following elements to the pattern" do - eval(<<~RUBY).should == [[0, 1], [3, 4]] - case [0, 1, 2, 3, 4] - in [*pre, 2, *post] - [pre, post] - else - false - end - RUBY - end - - it "can capture the entirety of the pattern" do - eval(<<~RUBY).should == [0, 1, 2, 3, 4] - case [0, 1, 2, 3, 4] - in [*everything] - everything - else - false - end - RUBY - end - - it "will match an empty Array-like structure" do - eval(<<~RUBY).should == [] - case [] - in [*everything] - everything - else - false - end - RUBY - end - - it "can be nested" do - eval(<<~RUBY).should == [[0, [2, 4, 6]], [[4, 16, 64]], 27] - case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]] - in [*pre, [*, 9, a], *post] - [pre, post, a] - else - false - end - RUBY - end + end - it "can be nested with an array pattern" do - eval(<<~RUBY).should == [[4, 16, 64]] - case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]] - in [_, _, [*, 9, *], *post] - post - else - false + describe "find pattern" do + it "captures preceding elements to the pattern" do + eval(<<~RUBY).should == [0, 1] + case [0, 1, 2, 3] + in [*pre, 2, 3] + pre + else + false + end + RUBY + end + + it "captures following elements to the pattern" do + eval(<<~RUBY).should == [2, 3] + case [0, 1, 2, 3] + in [0, 1, *post] + post + else + false + end + RUBY + end + + it "captures both preceding and following elements to the pattern" do + eval(<<~RUBY).should == [[0, 1], [3, 4]] + case [0, 1, 2, 3, 4] + in [*pre, 2, *post] + [pre, post] + else + false + end + RUBY + end + + it "can capture the entirety of the pattern" do + eval(<<~RUBY).should == [0, 1, 2, 3, 4] + case [0, 1, 2, 3, 4] + in [*everything] + everything + else + false + end + RUBY + end + + it "will match an empty Array-like structure" do + eval(<<~RUBY).should == [] + case [] + in [*everything] + everything + else + false + end + RUBY end - RUBY + end end - it "can be nested within a hash pattern" do - eval(<<~RUBY).should == [27] - case {a: [3, 9, 27]} - in {a: [*, 9, *post]} - post - else - false + it "extends case expression with case/in construction" do + eval(<<~RUBY).should == :bar + case [0, 1] + in [0] + :foo + in [0, 1] + :bar end RUBY end - it "can nest hash and array patterns" do - eval(<<~RUBY).should == [42, 2] - case [0, {a: 42, b: [0, 1]}, {a: 42, b: [1, 2]}] - in [*, {a:, b: [1, c]}, *] - [a, c] - else - false + it "allows using then operator" do + eval(<<~RUBY).should == :bar + case [0, 1] + in [0] then :foo + in [0, 1] then :bar end RUBY end - end - - it "extends case expression with case/in construction" do - eval(<<~RUBY).should == :bar - case [0, 1] - in [0] - :foo - in [0, 1] - :bar - end - RUBY - end - - it "allows using then operator" do - eval(<<~RUBY).should == :bar - case [0, 1] - in [0] then :foo - in [0, 1] then :bar - end - RUBY - end - - describe "warning" do - before :each do - @experimental, Warning[:experimental] = Warning[:experimental], true - end - - after :each do - Warning[:experimental] = @experimental - end - context 'when regular form' do + describe "warning" do before :each do - @src = 'case [0, 1]; in [a, b]; end' + @experimental, Warning[:experimental] = Warning[:experimental], true end - it "does not warn about pattern matching is experimental feature" do - -> { eval @src }.should_not complain - end - end - - context 'when one-line form' do - before :each do - @src = '[0, 1] => [a, b]' + after :each do + Warning[:experimental] = @experimental end - ruby_version_is ""..."3.1" do - it "warns about pattern matching is experimental feature" do - -> { eval @src }.should complain(/pattern matching is experimental, and the behavior may change in future versions of Ruby!/i) + context 'when regular form' do + before :each do + @src = 'case [0, 1]; in [a, b]; end' end - end - ruby_version_is "3.1" do - it "does not warn about pattern matching is experimental feature" do - -> { eval @src }.should_not complain - end - end - end - end - - it "binds variables" do - eval(<<~RUBY).should == 1 - case [0, 1] - in [0, a] - a - end - RUBY - end - - it "cannot mix in and when operators" do - -> { - eval <<~RUBY - case [] - when 1 == 1 - in [] + ruby_version_is ""..."3.0" do + it "warns about pattern matching is experimental feature" do + -> { eval @src }.should complain(/pattern matching is experimental, and the behavior may change in future versions of Ruby!/i) + end end - RUBY - }.should raise_error(SyntaxError, /syntax error, unexpected `in'|\(eval\):3: syntax error, unexpected keyword_in/) - -> { - eval <<~RUBY - case [] - in [] - when 1 == 1 + ruby_version_is "3.0" do + it "does not warn about pattern matching is experimental feature" do + -> { eval @src }.should_not complain + end end - RUBY - }.should raise_error(SyntaxError, /syntax error, unexpected `when'|\(eval\):3: syntax error, unexpected keyword_when/) - end - - it "checks patterns until the first matching" do - eval(<<~RUBY).should == :bar - case [0, 1] - in [0] - :foo - in [0, 1] - :bar - in [0, 1] - :baz - end - RUBY - end - - it "executes else clause if no pattern matches" do - eval(<<~RUBY).should == false - case [0, 1] - in [0] - true - else - false end - RUBY - end - it "raises NoMatchingPatternError if no pattern matches and no else clause" do - -> { - eval <<~RUBY - case [0, 1] - in [0] - end - RUBY - }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) - end + context 'when one-line form' do + ruby_version_is '3.0' do + before :each do + @src = '[0, 1] => [a, b]' + end - it "raises NoMatchingPatternError if no pattern matches and evaluates the expression only once" do - evals = 0 - -> { - eval <<~RUBY - case (evals += 1; [0, 1]) - in [0] - end - RUBY - }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) - evals.should == 1 - end + ruby_version_is ""..."3.1" do + it "warns about pattern matching is experimental feature" do + -> { eval @src }.should complain(/pattern matching is experimental, and the behavior may change in future versions of Ruby!/i) + end + end - it "does not allow calculation or method calls in a pattern" do - -> { - eval <<~RUBY - case 0 - in 1 + 1 - true + ruby_version_is "3.1" do + it "does not warn about pattern matching is experimental feature" do + -> { eval @src }.should_not complain + end + end end - RUBY - }.should raise_error(SyntaxError, /unexpected/) - end - - it "evaluates the case expression once for multiple patterns, caching the result" do - eval(<<~RUBY).should == true - case (ScratchPad << :foo; 1) - in 0 - false - in 1 - true end - RUBY - - ScratchPad.recorded.should == [:foo] - end - - describe "guards" do - it "supports if guard" do - eval(<<~RUBY).should == false - case 0 - in 0 if false - true - else - false - end - RUBY - - eval(<<~RUBY).should == true - case 0 - in 0 if true - true - else - false - end - RUBY - end - - it "supports unless guard" do - eval(<<~RUBY).should == false - case 0 - in 0 unless true - true - else - false - end - RUBY - - eval(<<~RUBY).should == true - case 0 - in 0 unless false - true - else - false - end - RUBY end - it "makes bound variables visible in guard" do - eval(<<~RUBY).should == true + it "binds variables" do + eval(<<~RUBY).should == 1 case [0, 1] - in [a, 1] if a >= 0 - true + in [0, a] + a end RUBY end - it "does not evaluate guard if pattern does not match" do - eval <<~RUBY - case 0 - in 1 if (ScratchPad << :foo) || true - else - end - RUBY + it "cannot mix in and when operators" do + -> { + eval <<~RUBY + case [] + when 1 == 1 + in [] + end + RUBY + }.should raise_error(SyntaxError, /syntax error, unexpected `in'/) - ScratchPad.recorded.should == [] + -> { + eval <<~RUBY + case [] + in [] + when 1 == 1 + end + RUBY + }.should raise_error(SyntaxError, /syntax error, unexpected `when'/) end - it "takes guards into account when there are several matching patterns" do + it "checks patterns until the first matching" do eval(<<~RUBY).should == :bar - case 0 - in 0 if false + case [0, 1] + in [0] :foo - in 0 if true + in [0, 1] :bar + in [0, 1] + :baz end RUBY end - it "executes else clause if no guarded pattern matches" do + it "executes else clause if no pattern matches" do eval(<<~RUBY).should == false - case 0 - in 0 if false + case [0, 1] + in [0] true else false @@ -372,1044 +198,1105 @@ describe "Pattern matching" do RUBY end - it "raises NoMatchingPatternError if no guarded pattern matches and no else clause" do + it "raises NoMatchingPatternError if no pattern matches and no else clause" do -> { eval <<~RUBY case [0, 1] - in [0, 1] if false + in [0] end RUBY }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) end - end - - describe "value pattern" do - it "matches an object such that pattern === object" do - eval(<<~RUBY).should == true - case 0 - in 0 - true - end - RUBY - eval(<<~RUBY).should == true - case 0 - in (-1..1) - true - end - RUBY - - eval(<<~RUBY).should == true - case 0 - in Integer - true - end - RUBY - - eval(<<~RUBY).should == true - case "0" - in /0/ - true - end - RUBY - - eval(<<~RUBY).should == true - case "0" - in ->(s) { s == "0" } - true - end - RUBY + it "does not allow calculation or method calls in a pattern" do + -> { + eval <<~RUBY + case 0 + in 1 + 1 + true + end + RUBY + }.should raise_error(SyntaxError, /unexpected/) end - it "allows string literal with interpolation" do - x = "x" - + it "evaluates the case expression once for multiple patterns, caching the result" do eval(<<~RUBY).should == true - case "x" - in "#{x + ""}" + case (ScratchPad << :foo; 1) + in 0 + false + in 1 true end RUBY - end - end - - describe "variable pattern" do - it "matches a value and binds variable name to this value" do - eval(<<~RUBY).should == 0 - case 0 - in a - a - end - RUBY - end - it "makes bounded variable visible outside a case statement scope" do - eval(<<~RUBY).should == 0 - case 0 - in a - end - - a - RUBY + ScratchPad.recorded.should == [:foo] end - it "create local variables even if a pattern doesn't match" do - eval(<<~RUBY).should == [0, nil, nil] - case 0 - in a - in b - in c - end - - [a, b, c] - RUBY - end + describe "guards" do + it "supports if guard" do + eval(<<~RUBY).should == false + case 0 + in 0 if false + true + else + false + end + RUBY - it "allow using _ name to drop values" do - eval(<<~RUBY).should == 0 - case [0, 1] - in [a, _] - a - end - RUBY - end + eval(<<~RUBY).should == true + case 0 + in 0 if true + true + else + false + end + RUBY + end - it "supports using _ in a pattern several times" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in [0, _, _] - true - end - RUBY - end + it "supports unless guard" do + eval(<<~RUBY).should == false + case 0 + in 0 unless true + true + else + false + end + RUBY - it "supports using any name with _ at the beginning in a pattern several times" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in [0, _x, _x] - true - end - RUBY + eval(<<~RUBY).should == true + case 0 + in 0 unless false + true + else + false + end + RUBY + end - eval(<<~RUBY).should == true - case {a: 0, b: 1, c: 2} - in {a: 0, b: _x, c: _x} - true - end - RUBY - end + it "makes bound variables visible in guard" do + eval(<<~RUBY).should == true + case [0, 1] + in [a, 1] if a >= 0 + true + end + RUBY + end - it "does not support using variable name (except _) several times" do - -> { + it "does not evaluate guard if pattern does not match" do eval <<~RUBY - case [0] - in [a, a] + case 0 + in 1 if (ScratchPad << :foo) || true + else end RUBY - }.should raise_error(SyntaxError, /duplicated variable name/) - end - - it "supports existing variables in a pattern specified with ^ operator" do - a = 0 - - eval(<<~RUBY).should == true - case 0 - in ^a - true - end - RUBY - end - it "allows applying ^ operator to bound variables" do - eval(<<~RUBY).should == 1 - case [1, 1] - in [n, ^n] - n - end - RUBY + ScratchPad.recorded.should == [] + end - eval(<<~RUBY).should == false - case [1, 2] - in [n, ^n] - true - else - false - end - RUBY - end + it "takes guards into account when there are several matching patterns" do + eval(<<~RUBY).should == :bar + case 0 + in 0 if false + :foo + in 0 if true + :bar + end + RUBY + end - it "requires bound variable to be specified in a pattern before ^ operator when it relies on a bound variable" do - -> { - eval <<~RUBY - case [1, 2] - in [^n, n] + it "executes else clause if no guarded pattern matches" do + eval(<<~RUBY).should == false + case 0 + in 0 if false true else false end RUBY - }.should raise_error(SyntaxError, /n: no such local variable/) - end - end + end - describe "alternative pattern" do - it "matches if any of patterns matches" do - eval(<<~RUBY).should == true - case 0 - in 0 | 1 | 2 - true - end - RUBY + it "raises NoMatchingPatternError if no guarded pattern matches and no else clause" do + -> { + eval <<~RUBY + case [0, 1] + in [0, 1] if false + end + RUBY + }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) + end end - it "does not support variable binding" do - -> { - eval <<~RUBY - case [0, 1] - in [0, 0] | [0, a] + describe "value pattern" do + it "matches an object such that pattern === object" do + eval(<<~RUBY).should == true + case 0 + in 0 + true end RUBY - }.should raise_error(SyntaxError, /illegal variable in alternative pattern/) - end - - it "support underscore prefixed variables in alternation" do - eval(<<~RUBY).should == true - case [0, 1] - in [1, _] - false - in [0, 0] | [0, _a] - true - end - RUBY - end - it "can be used as a nested pattern" do - eval(<<~RUBY).should == true - case [[1], ["2"]] - in [[0] | nil, _] - false - in [[1], [1]] - false - in [[1], [2 | "2"]] + eval(<<~RUBY).should == true + case 0 + in (-1..1) true - end - RUBY + end + RUBY - eval(<<~RUBY).should == true - case [1, 2] - in [0, _] | {a: 0} - false - in {a: 1, b: 2} | [1, 2] + eval(<<~RUBY).should == true + case 0 + in Integer true - end - RUBY - end - end + end + RUBY - describe "AS pattern" do - it "binds a variable to a value if pattern matches" do - eval(<<~RUBY).should == 0 - case 0 - in Integer => n - n - end - RUBY - end + eval(<<~RUBY).should == true + case "0" + in /0/ + true + end + RUBY - it "can be used as a nested pattern" do - eval(<<~RUBY).should == [2, 3] - case [1, [2, 3]] - in [1, Array => ary] - ary - end - RUBY - end - end + eval(<<~RUBY).should == true + case "0" + in ->(s) { s == "0" } + true + end + RUBY + end - describe "Array pattern" do - it "supports form Constant(pat, pat, ...)" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in Array(0, 1, 2) - true - end - RUBY - end + it "allows string literal with interpolation" do + x = "x" - it "supports form Constant[pat, pat, ...]" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in Array[0, 1, 2] - true - end - RUBY + eval(<<~RUBY).should == true + case "x" + in "#{x + ""}" + true + end + RUBY + end end - it "supports form [pat, pat, ...]" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in [0, 1, 2] - true - end - RUBY - end + describe "variable pattern" do + it "matches a value and binds variable name to this value" do + eval(<<~RUBY).should == 0 + case 0 + in a + a + end + RUBY + end - it "supports form pat, pat, ..." do - eval(<<~RUBY).should == true - case [0, 1, 2] - in 0, 1, 2 - true - end - RUBY + it "makes bounded variable visible outside a case statement scope" do + eval(<<~RUBY).should == 0 + case 0 + in a + end - eval(<<~RUBY).should == 1 - case [0, 1, 2] - in 0, a, 2 a - end - RUBY + RUBY + end - eval(<<~RUBY).should == [1, 2] - case [0, 1, 2] - in 0, *rest - rest - end - RUBY - end + it "create local variables even if a pattern doesn't match" do + eval(<<~RUBY).should == [0, nil, nil] + case 0 + in a + in b + in c + end - it "matches an object with #deconstruct method which returns an array and each element in array matches element in pattern" do - obj = Object.new - def obj.deconstruct; [0, 1] end + [a, b, c] + RUBY + end - eval(<<~RUBY).should == true - case obj - in [Integer, Integer] - true - end - RUBY - end + it "allow using _ name to drop values" do + eval(<<~RUBY).should == 0 + case [0, 1] + in [a, _] + a + end + RUBY + end - it "calls #deconstruct once for multiple patterns, caching the result" do - obj = Object.new + it "supports using _ in a pattern several times" do + eval(<<~RUBY).should == true + case [0, 1, 2] + in [0, _, _] + true + end + RUBY + end - def obj.deconstruct - ScratchPad << :deconstruct - [0, 1] + it "supports using any name with _ at the beginning in a pattern several times" do + eval(<<~RUBY).should == true + case [0, 1, 2] + in [0, _x, _x] + true + end + RUBY + + eval(<<~RUBY).should == true + case {a: 0, b: 1, c: 2} + in {a: 0, b: _x, c: _x} + true + end + RUBY end - eval(<<~RUBY).should == true - case obj - in [1, 2] - false - in [0, 1] - true - end - RUBY + it "does not support using variable name (except _) several times" do + -> { + eval <<~RUBY + case [0] + in [a, a] + end + RUBY + }.should raise_error(SyntaxError, /duplicated variable name/) + end - ScratchPad.recorded.should == [:deconstruct] - end + it "supports existing variables in a pattern specified with ^ operator" do + a = 0 - it "calls #deconstruct even on objects that are already an array" do - obj = [1, 2] - def obj.deconstruct - ScratchPad << :deconstruct - [3, 4] + eval(<<~RUBY).should == true + case 0 + in ^a + true + end + RUBY end - eval(<<~RUBY).should == true - case obj - in [3, 4] - true - else - false - end - RUBY + it "allows applying ^ operator to bound variables" do + eval(<<~RUBY).should == 1 + case [1, 1] + in [n, ^n] + n + end + RUBY - ScratchPad.recorded.should == [:deconstruct] - end + eval(<<~RUBY).should == false + case [1, 2] + in [n, ^n] + true + else + false + end + RUBY + end - it "does not match object if Constant === object returns false" do - eval(<<~RUBY).should == false - case [0, 1, 2] - in String[0, 1, 2] - true - else - false - end - RUBY + it "requires bound variable to be specified in a pattern before ^ operator when it relies on a bound variable" do + -> { + eval <<~RUBY + case [1, 2] + in [^n, n] + true + else + false + end + RUBY + }.should raise_error(SyntaxError, /n: no such local variable/) + end end - it "does not match object without #deconstruct method" do - obj = Object.new - obj.should_receive(:respond_to?).with(:deconstruct) + describe "alternative pattern" do + it "matches if any of patterns matches" do + eval(<<~RUBY).should == true + case 0 + in 0 | 1 | 2 + true + end + RUBY + end - eval(<<~RUBY).should == false - case obj - in Object[] - true - else - false - end - RUBY - end + it "does not support variable binding" do + -> { + eval <<~RUBY + case [0, 1] + in [0, 0] | [0, a] + end + RUBY + }.should raise_error(SyntaxError, /illegal variable in alternative pattern/) + end - it "raises TypeError if #deconstruct method does not return array" do - obj = Object.new - def obj.deconstruct; "" end + it "support underscore prefixed variables in alternation" do + eval(<<~RUBY).should == true + case [0, 1] + in [1, _] + false + in [0, 0] | [0, _a] + true + end + RUBY + end - -> { - eval <<~RUBY - case obj - in Object[] - else + it "can be used as a nested pattern" do + eval(<<~RUBY).should == true + case [[1], ["2"]] + in [[0] | nil, _] + false + in [[1], [1]] + false + in [[1], [2 | "2"]] + true end RUBY - }.should raise_error(TypeError, /deconstruct must return Array/) + + eval(<<~RUBY).should == true + case [1, 2] + in [0, _] | {a: 0} + false + in {a: 1, b: 2} | [1, 2] + true + end + RUBY + end end - it "accepts a subclass of Array from #deconstruct" do - obj = Object.new - def obj.deconstruct - subarray = Class.new(Array).new(2) - def subarray.[](n) - n - end - subarray + describe "AS pattern" do + it "binds a variable to a value if pattern matches" do + eval(<<~RUBY).should == 0 + case 0 + in Integer => n + n + end + RUBY end - eval(<<~RUBY).should == true - case obj - in [1, 2] - false - in [0, 1] - true - end - RUBY + it "can be used as a nested pattern" do + eval(<<~RUBY).should == [2, 3] + case [1, [2, 3]] + in [1, Array => ary] + ary + end + RUBY + end end - it "does not match object if elements of array returned by #deconstruct method does not match elements in pattern" do - obj = Object.new - def obj.deconstruct; [1] end + describe "Array pattern" do + it "supports form Constant(pat, pat, ...)" do + eval(<<~RUBY).should == true + case [0, 1, 2] + in Array(0, 1, 2) + true + end + RUBY + end - eval(<<~RUBY).should == false - case obj - in Object[0] - true - else - false - end - RUBY - end + it "supports form Constant[pat, pat, ...]" do + eval(<<~RUBY).should == true + case [0, 1, 2] + in Array[0, 1, 2] + true + end + RUBY + end - it "binds variables" do - eval(<<~RUBY).should == [0, 1, 2] - case [0, 1, 2] - in [a, b, c] - [a, b, c] - end - RUBY - end + it "supports form [pat, pat, ...]" do + eval(<<~RUBY).should == true + case [0, 1, 2] + in [0, 1, 2] + true + end + RUBY + end - it "supports splat operator *rest" do - eval(<<~RUBY).should == [1, 2] - case [0, 1, 2] - in [0, *rest] - rest - end - RUBY - end + it "supports form pat, pat, ..." do + eval(<<~RUBY).should == true + case [0, 1, 2] + in 0, 1, 2 + true + end + RUBY - it "does not match partially by default" do - eval(<<~RUBY).should == false - case [0, 1, 2, 3] - in [1, 2] - true - else - false - end - RUBY - end + eval(<<~RUBY).should == 1 + case [0, 1, 2] + in 0, a, 2 + a + end + RUBY - it "does match partially from the array beginning if list + , syntax used" do - eval(<<~RUBY).should == true - case [0, 1, 2, 3] - in [0, 1,] - true - end - RUBY + eval(<<~RUBY).should == [1, 2] + case [0, 1, 2] + in 0, *rest + rest + end + RUBY + end - eval(<<~RUBY).should == true - case [0, 1, 2, 3] - in 0, 1,; - true - end - RUBY - end + it "matches an object with #deconstruct method which returns an array and each element in array matches element in pattern" do + obj = Object.new + def obj.deconstruct; [0, 1] end - it "matches [] with []" do - eval(<<~RUBY).should == true - case [] - in [] - true + eval(<<~RUBY).should == true + case obj + in [Integer, Integer] + true + end + RUBY + end + + ruby_version_is "3.0" do + it "calls #deconstruct once for multiple patterns, caching the result" do + obj = Object.new + + def obj.deconstruct + ScratchPad << :deconstruct + [0, 1] + end + + eval(<<~RUBY).should == true + case obj + in [1, 2] + false + in [0, 1] + true + end + RUBY + + ScratchPad.recorded.should == [:deconstruct] end - RUBY - end + end - it "matches anything with *" do - eval(<<~RUBY).should == true - case [0, 1] - in *; - true + it "calls #deconstruct even on objects that are already an array" do + obj = [1, 2] + def obj.deconstruct + ScratchPad << :deconstruct + [3, 4] end - RUBY - end - it "can be used as a nested pattern" do - eval(<<~RUBY).should == true - case [[1], ["2"]] - in [[0] | nil, _] + eval(<<~RUBY).should == true + case obj + in [3, 4] + true + else false - in [[1], [1]] + end + RUBY + + ScratchPad.recorded.should == [:deconstruct] + end + + it "does not match object if Constant === object returns false" do + eval(<<~RUBY).should == false + case [0, 1, 2] + in String[0, 1, 2] + true + else false - in [[1], [2 | "2"]] + end + RUBY + end + + it "does not match object without #deconstruct method" do + obj = Object.new + obj.should_receive(:respond_to?).with(:deconstruct) + + eval(<<~RUBY).should == false + case obj + in Object[] true + else + false + end + RUBY + end + + it "raises TypeError if #deconstruct method does not return array" do + obj = Object.new + def obj.deconstruct; "" end + + -> { + eval <<~RUBY + case obj + in Object[] + else + end + RUBY + }.should raise_error(TypeError, /deconstruct must return Array/) + end + + it "accepts a subclass of Array from #deconstruct" do + obj = Object.new + def obj.deconstruct + subarray = Class.new(Array).new(2) + def subarray.[](n) + n + end + subarray end - RUBY - eval(<<~RUBY).should == true - case [1, 2] - in [0, _] | {a: 0} + eval(<<~RUBY).should == true + case obj + in [1, 2] false - in {a: 1, b: 2} | [1, 2] + in [0, 1] true - end - RUBY - end - end + end + RUBY + end - describe "Hash pattern" do - it "supports form Constant(id: pat, id: pat, ...)" do - eval(<<~RUBY).should == true - case {a: 0, b: 1} - in Hash(a: 0, b: 1) - true - end - RUBY - end + it "does not match object if elements of array returned by #deconstruct method does not match elements in pattern" do + obj = Object.new + def obj.deconstruct; [1] end - it "supports form Constant[id: pat, id: pat, ...]" do - eval(<<~RUBY).should == true - case {a: 0, b: 1} - in Hash[a: 0, b: 1] - true - end - RUBY - end + eval(<<~RUBY).should == false + case obj + in Object[0] + true + else + false + end + RUBY + end - it "supports form {id: pat, id: pat, ...}" do - eval(<<~RUBY).should == true - case {a: 0, b: 1} - in {a: 0, b: 1} - true - end - RUBY - end + it "binds variables" do + eval(<<~RUBY).should == [0, 1, 2] + case [0, 1, 2] + in [a, b, c] + [a, b, c] + end + RUBY + end - it "supports form id: pat, id: pat, ..." do - eval(<<~RUBY).should == true - case {a: 0, b: 1} - in a: 0, b: 1 - true - end - RUBY + it "supports splat operator *rest" do + eval(<<~RUBY).should == [1, 2] + case [0, 1, 2] + in [0, *rest] + rest + end + RUBY + end - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in a: a, b: b - [a, b] - end - RUBY + it "does not match partially by default" do + eval(<<~RUBY).should == false + case [0, 1, 2, 3] + in [1, 2] + true + else + false + end + RUBY + end - eval(<<~RUBY).should == { b: 1, c: 2 } - case {a: 0, b: 1, c: 2} - in a: 0, **rest - rest - end - RUBY - end + it "does match partially from the array beginning if list + , syntax used" do + eval(<<~RUBY).should == true + case [0, 1, 2, 3] + in [0, 1,] + true + end + RUBY - it "supports a: which means a: a" do - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in Hash(a:, b:) - [a, b] - end - RUBY + eval(<<~RUBY).should == true + case [0, 1, 2, 3] + in 0, 1,; + true + end + RUBY + end - a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in Hash[a:, b:] - [a, b] - end - RUBY + it "matches [] with []" do + eval(<<~RUBY).should == true + case [] + in [] + true + end + RUBY + end - a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in {a:, b:} - [a, b] - end - RUBY + it "matches anything with *" do + eval(<<~RUBY).should == true + case [0, 1] + in *; + true + end + RUBY + end - a = nil - eval(<<~RUBY).should == [0, {b: 1, c: 2}] - case {a: 0, b: 1, c: 2} - in {a:, **rest} - [a, rest] - end - RUBY + it "can be used as a nested pattern" do + eval(<<~RUBY).should == true + case [[1], ["2"]] + in [[0] | nil, _] + false + in [[1], [1]] + false + in [[1], [2 | "2"]] + true + end + RUBY - a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in a:, b: - [a, b] - end - RUBY + eval(<<~RUBY).should == true + case [1, 2] + in [0, _] | {a: 0} + false + in {a: 1, b: 2} | [1, 2] + true + end + RUBY + end end - it "can mix key (a:) and key-value (a: b) declarations" do - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in Hash(a:, b: x) - [a, x] - end - RUBY - end + describe "Hash pattern" do + it "supports form Constant(id: pat, id: pat, ...)" do + eval(<<~RUBY).should == true + case {a: 0, b: 1} + in Hash(a: 0, b: 1) + true + end + RUBY + end - it "supports 'string': key literal" do - eval(<<~RUBY).should == true - case {a: 0} - in {"a": 0} - true - end - RUBY - end + it "supports form Constant[id: pat, id: pat, ...]" do + eval(<<~RUBY).should == true + case {a: 0, b: 1} + in Hash[a: 0, b: 1] + true + end + RUBY + end - it "does not support non-symbol keys" do - -> { - eval <<~RUBY - case {a: 1} - in {"a" => 1} + it "supports form {id: pat, id: pat, ...}" do + eval(<<~RUBY).should == true + case {a: 0, b: 1} + in {a: 0, b: 1} + true end RUBY - }.should raise_error(SyntaxError, /unexpected/) - end + end - it "does not support string interpolation in keys" do - x = "a" + it "supports form id: pat, id: pat, ..." do + eval(<<~RUBY).should == true + case {a: 0, b: 1} + in a: 0, b: 1 + true + end + RUBY - -> { - eval <<~'RUBY' - case {a: 1} - in {"#{x}": 1} + eval(<<~RUBY).should == [0, 1] + case {a: 0, b: 1} + in a: a, b: b + [a, b] end RUBY - }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed/) - end - it "raise SyntaxError when keys duplicate in pattern" do - -> { - eval <<~RUBY - case {a: 1} - in {a: 1, b: 2, a: 3} + eval(<<~RUBY).should == { b: 1, c: 2 } + case {a: 0, b: 1, c: 2} + in a: 0, **rest + rest end RUBY - }.should raise_error(SyntaxError, /duplicated key name/) - end + end - it "matches an object with #deconstruct_keys method which returns a Hash with equal keys and each value in Hash matches value in pattern" do - obj = Object.new - def obj.deconstruct_keys(*); {a: 1} end + it "supports a: which means a: a" do + eval(<<~RUBY).should == [0, 1] + case {a: 0, b: 1} + in Hash(a:, b:) + [a, b] + end + RUBY - eval(<<~RUBY).should == true - case obj - in {a: 1} - true - end - RUBY - end + a = b = nil + eval(<<~RUBY).should == [0, 1] + case {a: 0, b: 1} + in Hash[a:, b:] + [a, b] + end + RUBY + + a = b = nil + eval(<<~RUBY).should == [0, 1] + case {a: 0, b: 1} + in {a:, b:} + [a, b] + end + RUBY - it "calls #deconstruct_keys per pattern" do - obj = Object.new + a = nil + eval(<<~RUBY).should == [0, {b: 1, c: 2}] + case {a: 0, b: 1, c: 2} + in {a:, **rest} + [a, rest] + end + RUBY - def obj.deconstruct_keys(*) - ScratchPad << :deconstruct_keys - {a: 1} + a = b = nil + eval(<<~RUBY).should == [0, 1] + case {a: 0, b: 1} + in a:, b: + [a, b] + end + RUBY end - eval(<<~RUBY).should == true - case obj - in {b: 1} - false - in {a: 1} - true - end - RUBY + it "can mix key (a:) and key-value (a: b) declarations" do + eval(<<~RUBY).should == [0, 1] + case {a: 0, b: 1} + in Hash(a:, b: x) + [a, x] + end + RUBY + end - ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys] - end + it "supports 'string': key literal" do + eval(<<~RUBY).should == true + case {a: 0} + in {"a": 0} + true + end + RUBY + end - it "does not match object if Constant === object returns false" do - eval(<<~RUBY).should == false - case {a: 1} - in String[a: 1] - true - else - false - end - RUBY - end + it "does not support non-symbol keys" do + -> { + eval <<~RUBY + case {a: 1} + in {"a" => 1} + end + RUBY + }.should raise_error(SyntaxError, /unexpected/) + end - it "does not match object without #deconstruct_keys method" do - obj = Object.new - obj.should_receive(:respond_to?).with(:deconstruct_keys) + it "does not support string interpolation in keys" do + x = "a" - eval(<<~RUBY).should == false - case obj - in Object[a: 1] - true - else - false - end - RUBY - end + -> { + eval <<~'RUBY' + case {a: 1} + in {"#{x}": 1} + end + RUBY + }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed/) + end - it "does not match object if #deconstruct_keys method does not return Hash" do - obj = Object.new - def obj.deconstruct_keys(*); "" end + it "raise SyntaxError when keys duplicate in pattern" do + -> { + eval <<~RUBY + case {a: 1} + in {a: 1, b: 2, a: 3} + end + RUBY + }.should raise_error(SyntaxError, /duplicated key name/) + end - -> { - eval <<~RUBY + it "matches an object with #deconstruct_keys method which returns a Hash with equal keys and each value in Hash matches value in pattern" do + obj = Object.new + def obj.deconstruct_keys(*); {a: 1} end + + eval(<<~RUBY).should == true case obj - in Object[a: 1] + in {a: 1} + true end RUBY - }.should raise_error(TypeError, /deconstruct_keys must return Hash/) - end - - it "does not match object if #deconstruct_keys method returns Hash with non-symbol keys" do - obj = Object.new - def obj.deconstruct_keys(*); {"a" => 1} end - - eval(<<~RUBY).should == false - case obj - in Object[a: 1] - true - else - false - end - RUBY - end + end - it "does not match object if elements of Hash returned by #deconstruct_keys method does not match values in pattern" do - obj = Object.new - def obj.deconstruct_keys(*); {a: 1} end + it "calls #deconstruct_keys per pattern" do + obj = Object.new - eval(<<~RUBY).should == false - case obj - in Object[a: 2] - true - else - false + def obj.deconstruct_keys(*) + ScratchPad << :deconstruct_keys + {a: 1} end - RUBY - end - it "passes keys specified in pattern as arguments to #deconstruct_keys method" do - obj = Object.new + eval(<<~RUBY).should == true + case obj + in {b: 1} + false + in {a: 1} + true + end + RUBY - def obj.deconstruct_keys(*args) - ScratchPad << args - {a: 1, b: 2, c: 3} + ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys] end - eval <<~RUBY - case obj - in Object[a: 1, b: 2, c: 3] - end - RUBY + it "does not match object if Constant === object returns false" do + eval(<<~RUBY).should == false + case {a: 1} + in String[a: 1] + true + else + false + end + RUBY + end - ScratchPad.recorded.sort.should == [[[:a, :b, :c]]] - end + it "does not match object without #deconstruct_keys method" do + obj = Object.new + obj.should_receive(:respond_to?).with(:deconstruct_keys) - it "passes keys specified in pattern to #deconstruct_keys method if pattern contains double splat operator **" do - obj = Object.new + eval(<<~RUBY).should == false + case obj + in Object[a: 1] + true + else + false + end + RUBY + end - def obj.deconstruct_keys(*args) - ScratchPad << args - {a: 1, b: 2, c: 3} + it "does not match object if #deconstruct_keys method does not return Hash" do + obj = Object.new + def obj.deconstruct_keys(*); "" end + + -> { + eval <<~RUBY + case obj + in Object[a: 1] + end + RUBY + }.should raise_error(TypeError, /deconstruct_keys must return Hash/) end - eval <<~RUBY - case obj - in Object[a: 1, b: 2, **] - end - RUBY + it "does not match object if #deconstruct_keys method returns Hash with non-symbol keys" do + obj = Object.new + def obj.deconstruct_keys(*); {"a" => 1} end - ScratchPad.recorded.sort.should == [[[:a, :b]]] - end + eval(<<~RUBY).should == false + case obj + in Object[a: 1] + true + else + false + end + RUBY + end - it "passes nil to #deconstruct_keys method if pattern contains double splat operator **rest" do - obj = Object.new + it "does not match object if elements of Hash returned by #deconstruct_keys method does not match values in pattern" do + obj = Object.new + def obj.deconstruct_keys(*); {a: 1} end - def obj.deconstruct_keys(*args) - ScratchPad << args - {a: 1, b: 2} + eval(<<~RUBY).should == false + case obj + in Object[a: 2] + true + else + false + end + RUBY end - eval <<~RUBY - case obj - in Object[a: 1, **rest] - end - RUBY + it "passes keys specified in pattern as arguments to #deconstruct_keys method" do + obj = Object.new - ScratchPad.recorded.should == [[nil]] - end - - it "binds variables" do - eval(<<~RUBY).should == [0, 1, 2] - case {a: 0, b: 1, c: 2} - in {a: x, b: y, c: z} - [x, y, z] + def obj.deconstruct_keys(*args) + ScratchPad << args + {a: 1, b: 2, c: 3} end - RUBY - end - it "supports double splat operator **rest" do - eval(<<~RUBY).should == {b: 1, c: 2} - case {a: 0, b: 1, c: 2} - in {a: 0, **rest} - rest - end - RUBY - end + eval <<~RUBY + case obj + in Object[a: 1, b: 2, c: 3] + end + RUBY - it "treats **nil like there should not be any other keys in a matched Hash" do - eval(<<~RUBY).should == true - case {a: 1, b: 2} - in {a: 1, b: 2, **nil} - true - end - RUBY + ScratchPad.recorded.sort.should == [[[:a, :b, :c]]] + end - eval(<<~RUBY).should == false - case {a: 1, b: 2} - in {a: 1, **nil} - true - else - false - end - RUBY - end + it "passes keys specified in pattern to #deconstruct_keys method if pattern contains double splat operator **" do + obj = Object.new - it "can match partially" do - eval(<<~RUBY).should == true - case {a: 1, b: 2} - in {a: 1} - true + def obj.deconstruct_keys(*args) + ScratchPad << args + {a: 1, b: 2, c: 3} end - RUBY - end - it "matches {} with {}" do - eval(<<~RUBY).should == true - case {} - in {} - true - end - RUBY - end + eval <<~RUBY + case obj + in Object[a: 1, b: 2, **] + end + RUBY - it "matches anything with **" do - eval(<<~RUBY).should == true - case {a: 1} - in **; - true - end - RUBY - end + ScratchPad.recorded.sort.should == [[[:a, :b]]] + end - it "can be used as a nested pattern" do - eval(<<~RUBY).should == true - case {a: {a: 1, b: 1}, b: {a: 1, b: 2}} - in {a: {a: 0}} - false - in {a: {a: 1}, b: {b: 1}} - false - in {a: {a: 1}, b: {b: 2}} - true - end - RUBY + it "passes nil to #deconstruct_keys method if pattern contains double splat operator **rest" do + obj = Object.new - eval(<<~RUBY).should == true - case [{a: 1, b: [1]}, {a: 1, c: ["2"]}] - in [{a:, c:},] - false - in [{a: 1, b:}, {a: 1, c: [Integer]}] - false - in [_, {a: 1, c: [String]}] - true + def obj.deconstruct_keys(*args) + ScratchPad << args + {a: 1, b: 2} end - RUBY - end - end - describe "refinements" do - it "are used for #deconstruct" do - refinery = Module.new do - refine Array do - def deconstruct - [0] + eval <<~RUBY + case obj + in Object[a: 1, **rest] end - end + RUBY + + ScratchPad.recorded.should == [[nil]] end - result = nil - Module.new do - using refinery + it "binds variables" do + eval(<<~RUBY).should == [0, 1, 2] + case {a: 0, b: 1, c: 2} + in {a: x, b: y, c: z} + [x, y, z] + end + RUBY + end - result = eval(<<~RUBY) - case [] - in [0] - true + it "supports double splat operator **rest" do + eval(<<~RUBY).should == {b: 1, c: 2} + case {a: 0, b: 1, c: 2} + in {a: 0, **rest} + rest end RUBY end - result.should == true - end + it "treats **nil like there should not be any other keys in a matched Hash" do + eval(<<~RUBY).should == true + case {a: 1, b: 2} + in {a: 1, b: 2, **nil} + true + end + RUBY - it "are used for #deconstruct_keys" do - refinery = Module.new do - refine Hash do - def deconstruct_keys(_) - {a: 0} + eval(<<~RUBY).should == false + case {a: 1, b: 2} + in {a: 1, **nil} + true + else + false end - end + RUBY end - result = nil - Module.new do - using refinery + it "can match partially" do + eval(<<~RUBY).should == true + case {a: 1, b: 2} + in {a: 1} + true + end + RUBY + end - result = eval(<<~RUBY) + it "matches {} with {}" do + eval(<<~RUBY).should == true case {} - in a: 0 + in {} true end RUBY end - result.should == true - end - - it "are used for #=== in constant pattern" do - refinery = Module.new do - refine Array.singleton_class do - def ===(obj) - obj.is_a?(Hash) + it "matches anything with **" do + eval(<<~RUBY).should == true + case {a: 1} + in **; + true end - end + RUBY end - result = nil - Module.new do - using refinery + it "can be used as a nested pattern" do + eval(<<~RUBY).should == true + case {a: {a: 1, b: 1}, b: {a: 1, b: 2}} + in {a: {a: 0}} + false + in {a: {a: 1}, b: {b: 1}} + false + in {a: {a: 1}, b: {b: 2}} + true + end + RUBY - result = eval(<<~RUBY) - case {} - in Array - true + eval(<<~RUBY).should == true + case [{a: 1, b: [1]}, {a: 1, c: ["2"]}] + in [{a:, c:},] + false + in [{a: 1, b:}, {a: 1, c: [Integer]}] + false + in [_, {a: 1, c: [String]}] + true end RUBY end - - result.should == true end - end - ruby_version_is "3.1" do - it "can omit parentheses in one line pattern matching" do - eval(<<~RUBY).should == [1, 2] - [1, 2] => a, b - [a, b] - RUBY + describe "refinements" do + it "are used for #deconstruct" do + refinery = Module.new do + refine Array do + def deconstruct + [0] + end + end + end - eval(<<~RUBY).should == 1 - {a: 1} => a: - a - RUBY - end + result = nil + Module.new do + using refinery - it "supports pinning instance variables" do - eval(<<~RUBY).should == true - @a = /a/ - case 'abc' - in ^@a - true + result = eval(<<~RUBY) + case [] + in [0] + true + end + RUBY end - RUBY - end - it "supports pinning class variables" do - result = nil - Module.new do - result = module_eval(<<~RUBY) - @@a = 0..10 + result.should == true + end - case 2 - in ^@@a - true + it "are used for #deconstruct_keys" do + refinery = Module.new do + refine Hash do + def deconstruct_keys(_) + {a: 0} + end end - RUBY - end + end - result.should == true - end + result = nil + Module.new do + using refinery - it "supports pinning global variables" do - eval(<<~RUBY).should == true - $a = /a/ - case 'abc' - in ^$a - true + result = eval(<<~RUBY) + case {} + in a: 0 + true + end + RUBY end - RUBY - end - it "supports pinning expressions" do - eval(<<~RUBY).should == true - case 'abc' - in ^(/a/) - true - end - RUBY + result.should == true + end - eval(<<~RUBY).should == true - case {name: '2.6', released_at: Time.new(2018, 12, 25)} - in {released_at: ^(Time.new(2010)..Time.new(2020))} - true + it "are used for #=== in constant pattern" do + refinery = Module.new do + refine Array.singleton_class do + def ===(obj) + obj.is_a?(Hash) + end + end end - RUBY - eval(<<~RUBY).should == true - case 0 - in ^(0+0) - true + result = nil + Module.new do + using refinery + + result = eval(<<~RUBY) + case {} + in Array + true + end + RUBY end - RUBY + + result.should == true + end + end + + ruby_version_is "3.1" do + it "can omit parentheses in one line pattern matching" do + eval(<<~RUBY).should == [1, 2] + [1, 2] => a, b + [a, b] + RUBY + + eval(<<~RUBY).should == 1 + {a: 1} => a: + a + RUBY + end end end end diff --git a/spec/ruby/language/precedence_spec.rb b/spec/ruby/language/precedence_spec.rb index c5adcca2c0..5a3c2861ce 100644 --- a/spec/ruby/language/precedence_spec.rb +++ b/spec/ruby/language/precedence_spec.rb @@ -14,44 +14,46 @@ require_relative 'fixtures/precedence' # the level below (as well as showing associativity within # the precedence level). -# Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide' -# Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324 -# -# Table 22.4. Ruby operators (high to low precedence) -# Method Operator Description -# ----------------------------------------------------------------------- -# :: . -# x* [ ] [ ]= Element reference, element set -# x ** Exponentiation -# x ! ~ + - Not, complement, unary plus and minus -# (method names for the last two are +@ and -@) -# x * / % Multiply, divide, and modulo -# x + - Plus and minus -# x >> << Right and left shift -# x & “And” (bitwise for integers) -# x ^ | Exclusive “or” and regular “or” (bitwise for integers) -# x <= < > >= Comparison operators -# x <=> == === != =~ !~ Equality and pattern match operators (!= -# and !~ may not be defined as methods) -# && Logical “and” -# || Logical “or” -# .. ... Range (inclusive and exclusive) -# ? : Ternary if-then-else -# = %= /= -= += |= &= Assignment -# >>= <<= *= &&= ||= **= -# defined? Check if symbol defined -# not Logical negation -# or and Logical composition -# if unless while until Expression modifiers -# begin/end Block expression -# ----------------------------------------------------------------------- -# -# * Operators marked with 'x' in the Method column are implemented as methods -# and can be overridden (except != and !~ as noted). (But see the specs -# below for implementations that define != and !~ as methods.) -# -# ** These are not included in the excerpted table but are shown here for -# completeness. +=begin +Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide' +Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324 + +Table 22.4. Ruby operators (high to low precedence) +Method Operator Description +----------------------------------------------------------------------- + :: . + x* [ ] [ ]= Element reference, element set + x ** Exponentiation + x ! ~ + - Not, complement, unary plus and minus + (method names for the last two are +@ and -@) + x * / % Multiply, divide, and modulo + x + - Plus and minus + x >> << Right and left shift + x & “And” (bitwise for integers) + x ^ | Exclusive “or” and regular “or” (bitwise for integers) + x <= < > >= Comparison operators + x <=> == === != =~ !~ Equality and pattern match operators (!= + and !~ may not be defined as methods) + && Logical “and” + || Logical “or” + .. ... Range (inclusive and exclusive) + ? : Ternary if-then-else + = %= /= -= += |= &= Assignment + >>= <<= *= &&= ||= **= + defined? Check if symbol defined + not Logical negation + or and Logical composition + if unless while until Expression modifiers + begin/end Block expression +----------------------------------------------------------------------- + +* Operators marked with 'x' in the Method column are implemented as methods +and can be overridden (except != and !~ as noted). (But see the specs +below for implementations that define != and !~ as methods.) + +** These are not included in the excerpted table but are shown here for +completeness. +=end # ----------------------------------------------------------------------- # It seems that this table is not correct anymore diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index fe865cc325..d311750200 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -7,33 +7,37 @@ require 'stringio' # Entries marked [r/o] are read-only and an error will be raised of the program attempts to # modify them. Entries marked [thread] are thread local. -# Exception Information -# --------------------------------------------------------------------------------------------------- -# -# $! Exception The exception object passed to raise. [thread] -# $@ Array The stack backtrace generated by the last exception. [thread] - -# Pattern Matching Variables -# --------------------------------------------------------------------------------------------------- -# -# These variables are set to nil after an unsuccessful pattern match. -# -# $& String The string matched (following a successful pattern match). This variable is -# local to the current scope. [r/o, thread] -# $+ String The contents of the highest-numbered group matched following a successful -# pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “t”. This -# variable is local to the current scope. [r/o, thread] -# $` String The string preceding the match in a successful pattern match. This variable -# is local to the current scope. [r/o, thread] -# $' String The string following the match in a successful pattern match. This variable -# is local to the current scope. [r/o, thread] -# $1 to $<N> String The contents of successive groups matched in a successful pattern match. In -# "cat" =~/(c|a)(t|z)/, $1 will be set to “a” and $2 to “t”. This variable -# is local to the current scope. [r/o, thread] -# $~ MatchData An object that encapsulates the results of a successful pattern match. The -# variables $&, $`, $', and $1 to $<N> are all derived from $~. Assigning to $~ -# changes the values of these derived variables. This variable is local to the -# current scope. [thread] +=begin +Exception Information +--------------------------------------------------------------------------------------------------- + +$! Exception The exception object passed to raise. [thread] +$@ Array The stack backtrace generated by the last exception. [thread] +=end + +=begin +Pattern Matching Variables +--------------------------------------------------------------------------------------------------- + +These variables are set to nil after an unsuccessful pattern match. + +$& String The string matched (following a successful pattern match). This variable is + local to the current scope. [r/o, thread] +$+ String The contents of the highest-numbered group matched following a successful + pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “t”. This + variable is local to the current scope. [r/o, thread] +$` String The string preceding the match in a successful pattern match. This variable + is local to the current scope. [r/o, thread] +$' String The string following the match in a successful pattern match. This variable + is local to the current scope. [r/o, thread] +$1 to $<N> String The contents of successive groups matched in a successful pattern match. In + "cat" =~/(c|a)(t|z)/, $1 will be set to “a” and $2 to “t”. This variable + is local to the current scope. [r/o, thread] +$~ MatchData An object that encapsulates the results of a successful pattern match. The + variables $&, $`, $', and $1 to $<N> are all derived from $~. Assigning to $~ + changes the values of these derived variables. This variable is local to the + current scope. [thread] +=end describe "Predefined global $~" do @@ -502,39 +506,41 @@ describe "Predefined global $!" do end end -# Input/Output Variables -# --------------------------------------------------------------------------------------------------- -# -# $/ String The input record separator (newline by default). This is the value that rou- -# tines such as Kernel#gets use to determine record boundaries. If set to -# nil, gets will read the entire file. -# $-0 String Synonym for $/. -# $\ String The string appended to the output of every call to methods such as -# Kernel#print and IO#write. The default value is nil. -# $, String The separator string output between the parameters to methods such as -# Kernel#print and Array#join. Defaults to nil, which adds no text. -# $. Integer The number of the last line read from the current input file. -# $; String The default separator pattern used by String#split. May be set from the -# command line using the -F flag. -# $< Object An object that provides access to the concatenation of the contents of all -# the files given as command-line arguments or $stdin (in the case where -# there are no arguments). $< supports methods similar to a File object: -# binmode, close, closed?, each, each_byte, each_line, eof, eof?, -# file, filename, fileno, getc, gets, lineno, lineno=, path, pos, pos=, -# read, readchar, readline, readlines, rewind, seek, skip, tell, to_a, -# to_i, to_io, to_s, along with the methods in Enumerable. The method -# file returns a File object for the file currently being read. This may change -# as $< reads through the files on the command line. [r/o] -# $> IO The destination of output for Kernel#print and Kernel#printf. The -# default value is $stdout. -# $_ String The last line read by Kernel#gets or Kernel#readline. Many string- -# related functions in the Kernel module operate on $_ by default. The vari- -# able is local to the current scope. [thread] -# $-F String Synonym for $;. -# $stderr IO The current standard error output. -# $stdin IO The current standard input. -# $stdout IO The current standard output. Assignment to $stdout is deprecated: use -# $stdout.reopen instead. +=begin +Input/Output Variables +--------------------------------------------------------------------------------------------------- + +$/ String The input record separator (newline by default). This is the value that rou- + tines such as Kernel#gets use to determine record boundaries. If set to + nil, gets will read the entire file. +$-0 String Synonym for $/. +$\ String The string appended to the output of every call to methods such as + Kernel#print and IO#write. The default value is nil. +$, String The separator string output between the parameters to methods such as + Kernel#print and Array#join. Defaults to nil, which adds no text. +$. Integer The number of the last line read from the current input file. +$; String The default separator pattern used by String#split. May be set from the + command line using the -F flag. +$< Object An object that provides access to the concatenation of the contents of all + the files given as command-line arguments or $stdin (in the case where + there are no arguments). $< supports methods similar to a File object: + binmode, close, closed?, each, each_byte, each_line, eof, eof?, + file, filename, fileno, getc, gets, lineno, lineno=, path, pos, pos=, + read, readchar, readline, readlines, rewind, seek, skip, tell, to_a, + to_i, to_io, to_s, along with the methods in Enumerable. The method + file returns a File object for the file currently being read. This may change + as $< reads through the files on the command line. [r/o] +$> IO The destination of output for Kernel#print and Kernel#printf. The + default value is $stdout. +$_ String The last line read by Kernel#gets or Kernel#readline. Many string- + related functions in the Kernel module operate on $_ by default. The vari- + able is local to the current scope. [thread] +$-F String Synonym for $;. +$stderr IO The current standard error output. +$stdin IO The current standard input. +$stdout IO The current standard output. Assignment to $stdout is deprecated: use + $stdout.reopen instead. +=end describe "Predefined global $/" do before :each do @@ -564,6 +570,7 @@ describe "Predefined global $/" do ($/ = "xyz").should == "xyz" end + it "changes $-0" do $/ = "xyz" $-0.should equal($/) @@ -634,45 +641,6 @@ describe "Predefined global $-0" do end end -describe "Predefined global $\\" do - before :each do - @verbose, $VERBOSE = $VERBOSE, nil - @dollar_backslash = $\ - end - - after :each do - $\ = @dollar_backslash - $VERBOSE = @verbose - end - - it "can be assigned a String" do - str = "abc" - $\ = str - $\.should equal(str) - end - - it "can be assigned nil" do - $\ = nil - $\.should be_nil - end - - it "returns the value assigned" do - ($\ = "xyz").should == "xyz" - end - - it "does not call #to_str to convert the object to a String" do - obj = mock("$\\ value") - obj.should_not_receive(:to_str) - - -> { $\ = obj }.should raise_error(TypeError) - end - - it "raises a TypeError if assigned not String" do - -> { $\ = 1 }.should raise_error(TypeError) - -> { $\ = true }.should raise_error(TypeError) - end -end - describe "Predefined global $," do after :each do $, = nil @@ -686,8 +654,10 @@ describe "Predefined global $," do -> { $, = Object.new }.should raise_error(TypeError) end - it "warns if assigned non-nil" do - -> { $, = "_" }.should complain(/warning: `\$,' is deprecated/) + ruby_version_is "2.7" do + it "warns if assigned non-nil" do + -> { $, = "_" }.should complain(/warning: `\$,' is deprecated/) + end end end @@ -723,8 +693,10 @@ describe "Predefined global $;" do $; = nil end - it "warns if assigned non-nil" do - -> { $; = "_" }.should complain(/warning: `\$;' is deprecated/) + ruby_version_is "2.7" do + it "warns if assigned non-nil" do + -> { $; = "_" }.should complain(/warning: `\$;' is deprecated/) + end end end @@ -797,52 +769,54 @@ describe "Predefined global $_" do end end -# Execution Environment Variables -# --------------------------------------------------------------------------------------------------- -# -# $0 String The name of the top-level Ruby program being executed. Typically this will -# be the program’s filename. On some operating systems, assigning to this -# variable will change the name of the process reported (for example) by the -# ps(1) command. -# $* Array An array of strings containing the command-line options from the invoca- -# tion of the program. Options used by the Ruby interpreter will have been -# removed. [r/o] -# $" Array An array containing the filenames of modules loaded by require. [r/o] -# $$ Integer The process number of the program being executed. [r/o] -# $? Process::Status The exit status of the last child process to terminate. [r/o, thread] -# $: Array An array of strings, where each string specifies a directory to be searched for -# Ruby scripts and binary extensions used by the load and require methods. -# The initial value is the value of the arguments passed via the -I command- -# line option, followed by an installation-defined standard library location, fol- -# lowed by the current directory (“.”). This variable may be set from within a -# program to alter the default search path; typically, programs use $: << dir -# to append dir to the path. [r/o] -# $-a Object True if the -a option is specified on the command line. [r/o] -# $-d Object Synonym for $DEBUG. -# $DEBUG Object Set to true if the -d command-line option is specified. -# __FILE__ String The name of the current source file. [r/o] -# $F Array The array that receives the split input line if the -a command-line option is -# used. -# $FILENAME String The name of the current input file. Equivalent to $<.filename. [r/o] -# $-i String If in-place edit mode is enabled (perhaps using the -i command-line -# option), $-i holds the extension used when creating the backup file. If you -# set a value into $-i, enables in-place edit mode. -# $-I Array Synonym for $:. [r/o] -# $-K String Sets the multibyte coding system for strings and regular expressions. Equiv- -# alent to the -K command-line option. -# $-l Object Set to true if the -l option (which enables line-end processing) is present -# on the command line. [r/o] -# __LINE__ String The current line number in the source file. [r/o] -# $LOAD_PATH Array A synonym for $:. [r/o] -# $-p Object Set to true if the -p option (which puts an implicit while gets . . . end -# loop around your program) is present on the command line. [r/o] -# $VERBOSE Object Set to true if the -v, --version, -W, or -w option is specified on the com- -# mand line. Set to false if no option, or -W1 is given. Set to nil if -W0 -# was specified. Setting this option to true causes the interpreter and some -# library routines to report additional information. Setting to nil suppresses -# all warnings (including the output of Kernel.warn). -# $-v Object Synonym for $VERBOSE. -# $-w Object Synonym for $VERBOSE. +=begin +Execution Environment Variables +--------------------------------------------------------------------------------------------------- + +$0 String The name of the top-level Ruby program being executed. Typically this will + be the program’s filename. On some operating systems, assigning to this + variable will change the name of the process reported (for example) by the + ps(1) command. +$* Array An array of strings containing the command-line options from the invoca- + tion of the program. Options used by the Ruby interpreter will have been + removed. [r/o] +$" Array An array containing the filenames of modules loaded by require. [r/o] +$$ Integer The process number of the program being executed. [r/o] +$? Process::Status The exit status of the last child process to terminate. [r/o, thread] +$: Array An array of strings, where each string specifies a directory to be searched for + Ruby scripts and binary extensions used by the load and require methods. + The initial value is the value of the arguments passed via the -I command- + line option, followed by an installation-defined standard library location, fol- + lowed by the current directory (“.”). This variable may be set from within a + program to alter the default search path; typically, programs use $: << dir + to append dir to the path. [r/o] +$-a Object True if the -a option is specified on the command line. [r/o] +$-d Object Synonym for $DEBUG. +$DEBUG Object Set to true if the -d command-line option is specified. +__FILE__ String The name of the current source file. [r/o] +$F Array The array that receives the split input line if the -a command-line option is + used. +$FILENAME String The name of the current input file. Equivalent to $<.filename. [r/o] +$-i String If in-place edit mode is enabled (perhaps using the -i command-line + option), $-i holds the extension used when creating the backup file. If you + set a value into $-i, enables in-place edit mode. +$-I Array Synonym for $:. [r/o] +$-K String Sets the multibyte coding system for strings and regular expressions. Equiv- + alent to the -K command-line option. +$-l Object Set to true if the -l option (which enables line-end processing) is present + on the command line. [r/o] +__LINE__ String The current line number in the source file. [r/o] +$LOAD_PATH Array A synonym for $:. [r/o] +$-p Object Set to true if the -p option (which puts an implicit while gets . . . end + loop around your program) is present on the command line. [r/o] +$VERBOSE Object Set to true if the -v, --version, -W, or -w option is specified on the com- + mand line. Set to false if no option, or -W1 is given. Set to nil if -W0 + was specified. Setting this option to true causes the interpreter and some + library routines to report additional information. Setting to nil suppresses + all warnings (including the output of Kernel.warn). +$-v Object Synonym for $VERBOSE. +$-w Object Synonym for $VERBOSE. +=end describe "Execution variable $:" do it "is initialized to an array of strings" do $:.is_a?(Array).should == true @@ -861,8 +835,6 @@ describe "Execution variable $:" do it "can be changed via <<" do $: << "foo" $:.should include("foo") - ensure - $:.delete("foo") end it "is read-only" do @@ -878,16 +850,6 @@ describe "Execution variable $:" do $-I = [] }.should raise_error(NameError) end - - it "default $LOAD_PATH entries until sitelibdir included have @gem_prelude_index set" do - skip "no sense in ruby itself" if MSpecScript.instance_variable_defined?(:@testing_ruby) - - $:.should.include?(RbConfig::CONFIG['sitelibdir']) - idx = $:.index(RbConfig::CONFIG['sitelibdir']) - - $:[idx..-1].all? { |p| p.instance_variable_defined?(:@gem_prelude_index) }.should be_true - $:[0...idx].all? { |p| !p.instance_variable_defined?(:@gem_prelude_index) }.should be_true - end end describe "Global variable $\"" do @@ -979,10 +941,6 @@ describe "Global variable $VERBOSE" do $VERBOSE = @verbose end - it "is false by default" do - $VERBOSE.should be_false - end - it "converts truthy values to true" do [true, 1, 0, [], ""].each do |true_value| $VERBOSE = true_value @@ -1037,7 +995,7 @@ describe "Global variable $0" do it "is the path given as the main script and the same as __FILE__" do script = "fixtures/dollar_zero.rb" - Dir.chdir(__dir__) do + Dir.chdir(File.dirname(__FILE__)) do ruby_exe(script).should == "#{script}\n#{script}\nOK" end end @@ -1064,20 +1022,22 @@ describe "Global variable $0" do end end -# Standard Objects -# --------------------------------------------------------------------------------------------------- -# -# ARGF Object A synonym for $<. -# ARGV Array A synonym for $*. -# ENV Object A hash-like object containing the program’s environment variables. An -# instance of class Object, ENV implements the full set of Hash methods. Used -# to query and set the value of an environment variable, as in ENV["PATH"] -# and ENV["term"]="ansi". -# false FalseClass Singleton instance of class FalseClass. [r/o] -# nil NilClass The singleton instance of class NilClass. The value of uninitialized -# instance and global variables. [r/o] -# self Object The receiver (object) of the current method. [r/o] -# true TrueClass Singleton instance of class TrueClass. [r/o] +=begin +Standard Objects +--------------------------------------------------------------------------------------------------- + +ARGF Object A synonym for $<. +ARGV Array A synonym for $*. +ENV Object A hash-like object containing the program’s environment variables. An + instance of class Object, ENV implements the full set of Hash methods. Used + to query and set the value of an environment variable, as in ENV["PATH"] + and ENV["term"]="ansi". +false FalseClass Singleton instance of class FalseClass. [r/o] +nil NilClass The singleton instance of class NilClass. The value of uninitialized + instance and global variables. [r/o] +self Object The receiver (object) of the current method. [r/o] +true TrueClass Singleton instance of class TrueClass. [r/o] +=end describe "The predefined standard objects" do it "includes ARGF" do @@ -1130,51 +1090,80 @@ describe "The self pseudo-variable" do end end -# Global Constants -# --------------------------------------------------------------------------------------------------- -# -# The following constants are defined by the Ruby interpreter. -# -# DATA IO If the main program file contains the directive __END__, then -# the constant DATA will be initialized so that reading from it will -# return lines following __END__ from the source file. -# FALSE FalseClass Synonym for false (deprecated, removed in Ruby 3). -# NIL NilClass Synonym for nil (deprecated, removed in Ruby 3). -# RUBY_PLATFORM String The identifier of the platform running this program. This string -# is in the same form as the platform identifier used by the GNU -# configure utility (which is not a coincidence). -# RUBY_RELEASE_DATE String The date of this release. -# RUBY_VERSION String The version number of the interpreter. -# STDERR IO The actual standard error stream for the program. The initial -# value of $stderr. -# STDIN IO The actual standard input stream for the program. The initial -# value of $stdin. -# STDOUT IO The actual standard output stream for the program. The initial -# value of $stdout. -# SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash, -# Ruby will store an entry containing the contents of each file it -# parses, with the file’s name as the key and an array of strings as -# the value. -# TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level— -# the level where programs are initially executed. -# TRUE TrueClass Synonym for true (deprecated, removed in Ruby 3). +=begin +Global Constants +--------------------------------------------------------------------------------------------------- + +The following constants are defined by the Ruby interpreter. + +DATA IO If the main program file contains the directive __END__, then + the constant DATA will be initialized so that reading from it will + return lines following __END__ from the source file. +FALSE FalseClass Synonym for false (deprecated, removed in Ruby 3). +NIL NilClass Synonym for nil (deprecated, removed in Ruby 3). +RUBY_PLATFORM String The identifier of the platform running this program. This string + is in the same form as the platform identifier used by the GNU + configure utility (which is not a coincidence). +RUBY_RELEASE_DATE String The date of this release. +RUBY_VERSION String The version number of the interpreter. +STDERR IO The actual standard error stream for the program. The initial + value of $stderr. +STDIN IO The actual standard input stream for the program. The initial + value of $stdin. +STDOUT IO The actual standard output stream for the program. The initial + value of $stdout. +SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash, + Ruby will store an entry containing the contents of each file it + parses, with the file’s name as the key and an array of strings as + the value. +TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level— + the level where programs are initially executed. +TRUE TrueClass Synonym for true (deprecated, removed in Ruby 3). +=end describe "The predefined global constants" do describe "TRUE" do - it "is no longer defined" do - Object.const_defined?(:TRUE).should == false + ruby_version_is "3.0" do + it "is no longer defined" do + Object.const_defined?(:TRUE).should == false + end + end + + ruby_version_is ""..."3.0" do + it "includes TRUE" do + Object.const_defined?(:TRUE).should == true + -> { TRUE }.should complain(/constant ::TRUE is deprecated/) + end end end describe "FALSE" do - it "is no longer defined" do - Object.const_defined?(:FALSE).should == false + ruby_version_is "3.0" do + it "is no longer defined" do + Object.const_defined?(:FALSE).should == false + end + end + + ruby_version_is ""..."3.0" do + it "includes FALSE" do + Object.const_defined?(:FALSE).should == true + -> { FALSE }.should complain(/constant ::FALSE is deprecated/) + end end end describe "NIL" do - it "is no longer defined" do - Object.const_defined?(:NIL).should == false + ruby_version_is "3.0" do + it "is no longer defined" do + Object.const_defined?(:NIL).should == false + end + end + + ruby_version_is ""..."3.0" do + it "includes NIL" do + Object.const_defined?(:NIL).should == true + -> { NIL }.should complain(/constant ::NIL is deprecated/) + end end end @@ -1315,57 +1304,32 @@ describe "The predefined global constant" do end end -describe "$LOAD_PATH.resolve_feature_path" do - it "returns what will be loaded without actual loading, .rb file" do - extension, path = $LOAD_PATH.resolve_feature_path('set') - extension.should == :rb - path.should.end_with?('/set.rb') - end - - it "returns what will be loaded without actual loading, .so file" do - require 'rbconfig' - skip "no dynamically loadable standard extension" if RbConfig::CONFIG["EXTSTATIC"] == "static" +ruby_version_is "2.7" do + describe "$LOAD_PATH.resolve_feature_path" do + it "returns what will be loaded without actual loading, .rb file" do + extension, path = $LOAD_PATH.resolve_feature_path('set') + extension.should == :rb + path.should.end_with?('/set.rb') + end - extension, path = $LOAD_PATH.resolve_feature_path('etc') - extension.should == :so - path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}") - end + it "returns what will be loaded without actual loading, .so file" do + require 'rbconfig' - ruby_version_is ""..."3.1" do - it "raises LoadError if feature cannot be found" do - -> { $LOAD_PATH.resolve_feature_path('noop') }.should raise_error(LoadError) + extension, path = $LOAD_PATH.resolve_feature_path('etc') + extension.should == :so + path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}") end - end - ruby_version_is "3.1" do - it "return nil if feature cannot be found" do - $LOAD_PATH.resolve_feature_path('noop').should be_nil + ruby_version_is "2.7"..."3.1" do + it "raises LoadError if feature cannot be found" do + -> { $LOAD_PATH.resolve_feature_path('noop') }.should raise_error(LoadError) + end end - end -end - -# Some other pre-defined global variables - -describe "Predefined global $=" do - before :each do - @verbose, $VERBOSE = $VERBOSE, nil - @dollar_assign = $= - end - - after :each do - $= = @dollar_assign - $VERBOSE = @verbose - end - - it "warns when accessed" do - -> { a = $= }.should complain(/is no longer effective/) - end - it "warns when assigned" do - -> { $= = "_" }.should complain(/is no longer effective/) - end - - it "returns the value assigned" do - ($= = "xyz").should == "xyz" + ruby_version_is "3.1" do + it "return nil if feature cannot be found" do + $LOAD_PATH.resolve_feature_path('noop').should be_nil + end + end end end diff --git a/spec/ruby/language/proc_spec.rb b/spec/ruby/language/proc_spec.rb index cc69b7799c..ef4a43bed6 100644 --- a/spec/ruby/language/proc_spec.rb +++ b/spec/ruby/language/proc_spec.rb @@ -161,18 +161,6 @@ describe "A Proc" do end end - describe "taking |*a, b| arguments" do - it "assigns [] to the argument when passed no values" do - proc { |*a, b| [a, b] }.call.should == [[], nil] - end - end - - describe "taking |a, *b, c| arguments" do - it "assigns [] to the argument when passed no values" do - proc { |a, *b, c| [a, b, c] }.call.should == [nil, [], nil] - end - end - describe "taking |a, | arguments" do before :each do @l = lambda { |a, | a } @@ -235,15 +223,24 @@ describe "A Proc" do @p = proc { |*a, **kw| [a, kw] } end - it 'does not autosplat keyword arguments' do - @p.call([1, {a: 1}]).should == [[[1, {a: 1}]], {}] + ruby_version_is ""..."2.7" do + it 'autosplats keyword arguments' do + @p.call([1, {a: 1}]).should == [[1], {a: 1}] + end + end + + ruby_version_is "2.7"..."3.0" do + it 'autosplats keyword arguments and warns' do + -> { + @p.call([1, {a: 1}]).should == [[1], {a: 1}] + }.should complain(/warning: Using the last argument as keyword parameters is deprecated; maybe \*\* should be added to the call/) + end end - end - describe "taking |required keyword arguments, **kw| arguments" do - it "raises ArgumentError for missing required argument" do - p = proc { |a:, **kw| [a, kw] } - -> { p.call() }.should raise_error(ArgumentError) + ruby_version_is "3.0" do + it 'does not autosplat keyword arguments' do + @p.call([1, {a: 1}]).should == [[[1, {a: 1}]], {}] + end end end end diff --git a/spec/ruby/language/range_spec.rb b/spec/ruby/language/range_spec.rb index ccc9f55537..4cde7e9488 100644 --- a/spec/ruby/language/range_spec.rb +++ b/spec/ruby/language/range_spec.rb @@ -10,21 +10,21 @@ describe "Literal Ranges" do (1...10).should == Range.new(1, 10, true) end - it "creates a simple range as an object literal" do - ary = [] - 2.times do - ary.push(1..3) - end - ary[0].should.equal?(ary[1]) - end - it "creates endless ranges" do (1..).should == Range.new(1, nil) (1...).should == Range.new(1, nil, true) end - it "creates beginless ranges" do - (..1).should == Range.new(nil, 1) - (...1).should == Range.new(nil, 1, true) + ruby_version_is "3.0" do + it "is frozen" do + (42..).should.frozen? + end + end + + ruby_version_is "2.7" do + it "creates beginless ranges" do + eval("(..1)").should == Range.new(nil, 1) + eval("(...1)").should == Range.new(nil, 1, true) + end end end diff --git a/spec/ruby/language/regexp/character_classes_spec.rb b/spec/ruby/language/regexp/character_classes_spec.rb index 98d431a817..0cf1e9b6f4 100644 --- a/spec/ruby/language/regexp/character_classes_spec.rb +++ b/spec/ruby/language/regexp/character_classes_spec.rb @@ -609,13 +609,10 @@ describe "Regexp with character classes" do "루비(Ruby)".match(/\p{Hangul}+/u).to_a.should == ["루비"] end - it "supports negated property condition" do - "a".match(eval("/\P{L}/")).should be_nil - "1".match(eval("/\P{N}/")).should be_nil - end - - it "raises a RegexpError for an unterminated unicode property" do - -> { Regexp.new('\p{') }.should raise_error(RegexpError) + ruby_bug "#17340", ''...'3.0' do + it "raises a RegexpError for an unterminated unicode property" do + -> { Regexp.new('\p{') }.should raise_error(RegexpError) + end end it "supports \\X (unicode 9.0 with UTR #51 workarounds)" do diff --git a/spec/ruby/language/regexp/escapes_spec.rb b/spec/ruby/language/regexp/escapes_spec.rb index 16a4d8c23b..2e5fe5ad2e 100644 --- a/spec/ruby/language/regexp/escapes_spec.rb +++ b/spec/ruby/language/regexp/escapes_spec.rb @@ -2,10 +2,8 @@ require_relative '../../spec_helper' require_relative '../fixtures/classes' -# TODO: synchronize with spec/core/regexp/new_spec.rb - -# escaping is also tested there describe "Regexps with escape characters" do - it "supports escape sequences" do + it "they're supported" do /\t/.match("\t").to_a.should == ["\t"] # horizontal tab /\v/.match("\v").to_a.should == ["\v"] # vertical tab /\n/.match("\n").to_a.should == ["\n"] # newline @@ -17,7 +15,9 @@ describe "Regexps with escape characters" do # \nnn octal char (encoded byte value) end - it "supports quoting meta-characters via escape sequence" do + it "support quoting meta-characters via escape sequence" do + /\\/.match("\\").to_a.should == ["\\"] + /\//.match("/").to_a.should == ["/"] # parenthesis, etc /\(/.match("(").to_a.should == ["("] /\)/.match(")").to_a.should == [")"] @@ -25,8 +25,6 @@ describe "Regexps with escape characters" do /\]/.match("]").to_a.should == ["]"] /\{/.match("{").to_a.should == ["{"] /\}/.match("}").to_a.should == ["}"] - /\</.match("<").to_a.should == ["<"] - /\>/.match(">").to_a.should == [">"] # alternation separator /\|/.match("|").to_a.should == ["|"] # quantifiers @@ -39,81 +37,11 @@ describe "Regexps with escape characters" do /\$/.match("$").to_a.should == ["$"] end - it "supports quoting meta-characters via escape sequence when used as a terminator" do - # parenthesis, etc - # %r[[, %r((, etc literals - are forbidden - %r(\().match("(").to_a.should == ["("] - %r(\)).match(")").to_a.should == [")"] - %r)\().match("(").to_a.should == ["("] - %r)\)).match(")").to_a.should == [")"] - - %r[\[].match("[").to_a.should == ["["] - %r[\]].match("]").to_a.should == ["]"] - %r]\[].match("[").to_a.should == ["["] - %r]\]].match("]").to_a.should == ["]"] - - %r{\{}.match("{").to_a.should == ["{"] - %r{\}}.match("}").to_a.should == ["}"] - %r}\{}.match("{").to_a.should == ["{"] - %r}\}}.match("}").to_a.should == ["}"] - - %r<\<>.match("<").to_a.should == ["<"] - %r<\>>.match(">").to_a.should == [">"] - %r>\<>.match("<").to_a.should == ["<"] - %r>\>>.match(">").to_a.should == [">"] - - # alternation separator - %r|\||.match("|").to_a.should == ["|"] - # quantifiers - %r?\??.match("?").to_a.should == ["?"] - %r.\...match(".").to_a.should == ["."] - %r*\**.match("*").to_a.should == ["*"] - %r+\++.match("+").to_a.should == ["+"] - # line anchors - %r^\^^.match("^").to_a.should == ["^"] - %r$\$$.match("$").to_a.should == ["$"] - end - - it "supports quoting non-meta-characters via escape sequence when used as a terminator" do - non_meta_character_terminators = [ - '!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`', '/', '=', '~' - ] - - non_meta_character_terminators.each do |c| - pattern = eval("%r" + c + "\\" + c + c) - pattern.match(c).to_a.should == [c] - end - end - - it "does not change semantics of escaped non-meta-character when used as a terminator" do - all_terminators = [*("!".."/"), *(":".."@"), *("[".."`"), *("{".."~")] - meta_character_terminators = ["$", "^", "*", "+", ".", "?", "|", "}", ")", ">", "]"] - special_cases = ['(', '{', '[', '<', '\\'] - - # it should be equivalent to - # [ '!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`', '/', '=', '~' ] - non_meta_character_terminators = all_terminators - meta_character_terminators - special_cases - - non_meta_character_terminators.each do |c| - pattern = eval("%r" + c + "\\" + c + c) - pattern.should == /#{c}/ - end - end - - it "does not change semantics of escaped meta-character when used as a terminator" do - meta_character_terminators = ["$", "^", "*", "+", ".", "?", "|", "}", ")", ">", "]"] - - meta_character_terminators.each do |c| - pattern = eval("%r" + c + "\\" + c + c) - pattern.should == eval("/\\#{c}/") - end - end - it "allows any character to be escaped" do /\y/.match("y").to_a.should == ["y"] end - it "supports \\x (hex characters)" do + it "support \\x (hex characters)" do /\xA/.match("\nxyz").to_a.should == ["\n"] /\x0A/.match("\n").to_a.should == ["\n"] /\xAA/.match("\nA").should be_nil @@ -125,7 +53,7 @@ describe "Regexps with escape characters" do # \x{7HHHHHHH} wide hexadecimal char (character code point value) end - it "supports \\c (control characters)" do + it "support \\c (control characters)" do #/\c \c@\c`/.match("\00\00\00").to_a.should == ["\00\00\00"] /\c#\cc\cC/.match("\03\03\03").to_a.should == ["\03\03\03"] /\c'\cG\cg/.match("\a\a\a").to_a.should == ["\a\a\a"] diff --git a/spec/ruby/language/regexp/repetition_spec.rb b/spec/ruby/language/regexp/repetition_spec.rb index d76619688f..9a191d74e2 100644 --- a/spec/ruby/language/regexp/repetition_spec.rb +++ b/spec/ruby/language/regexp/repetition_spec.rb @@ -87,7 +87,9 @@ describe "Regexps with repetition" do /a+?*/.match("a")[0].should == "a" /(a+?)*/.match("a")[0].should == "a" - /a+?*/.match("aa")[0].should == "aa" + ruby_bug '#17341', ''...'3.0' do + /a+?*/.match("aa")[0].should == "aa" + end /(a+?)*/.match("aa")[0].should == "aa" # a+?+ should not be reduced, it should be equivalent to (a+?)+ @@ -98,7 +100,9 @@ describe "Regexps with repetition" do /a+?+/.match("a")[0].should == "a" /(a+?)+/.match("a")[0].should == "a" - /a+?+/.match("aa")[0].should == "aa" + ruby_bug '#17341', ''...'3.0' do + /a+?+/.match("aa")[0].should == "aa" + end /(a+?)+/.match("aa")[0].should == "aa" # both a**? and a+*? should be equivalent to (a+)?? diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb index cdafd29f49..f607fa6010 100644 --- a/spec/ruby/language/regexp_spec.rb +++ b/spec/ruby/language/regexp_spec.rb @@ -18,8 +18,10 @@ describe "Literal Regexps" do /Hello/.should be_kind_of(Regexp) end - it "is frozen" do - /Hello/.should.frozen? + ruby_version_is "3.0" do + it "is frozen" do + /Hello/.should.frozen? + end end it "caches the Regexp object" do @@ -94,6 +96,7 @@ describe "Literal Regexps" do /./.match("\0").to_a.should == ["\0"] end + it "supports | (alternations)" do /a|b/.match("a").to_a.should == ["a"] end @@ -112,7 +115,7 @@ describe "Literal Regexps" do /foo.(?<=\d)/.match("fooA foo1").to_a.should == ["foo1"] end - ruby_bug "#13671", ""..."3.4" do # https://bugs.ruby-lang.org/issues/13671 + ruby_bug "#13671", ""..."3.2" do # https://bugs.ruby-lang.org/issues/13671 it "handles a lookbehind with ss characters" do r = Regexp.new("(?<!dss)", Regexp::IGNORECASE) r.should =~ "✨" @@ -158,6 +161,26 @@ describe "Literal Regexps" do pattern.should_not =~ 'T' end + escapable_terminators = ['!', '"', '#', '%', '&', "'", ',', '-', ':', ';', '@', '_', '`'] + + it "supports escaping characters when used as a terminator" do + escapable_terminators.each do |c| + ref = "(?-mix:#{c})" + pattern = eval("%r" + c + "\\" + c + c) + pattern.to_s.should == ref + end + end + + it "treats an escaped non-escapable character normally when used as a terminator" do + all_terminators = [*("!".."/"), *(":".."@"), *("[".."`"), *("{".."~")] + special_cases = ['(', '{', '[', '<', '\\', '=', '~'] + (all_terminators - special_cases - escapable_terminators).each do |c| + ref = "(?-mix:\\#{c})" + pattern = eval("%r" + c + "\\" + c + c) + pattern.to_s.should == ref + end + end + it "support handling unicode 9.0 characters with POSIX bracket expressions" do char_lowercase = "\u{104D8}" # OSAGE SMALL LETTER A /[[:lower:]]/.match(char_lowercase).to_s.should == char_lowercase diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index b91b52fa0f..4d164b38c6 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -115,18 +115,6 @@ describe "The rescue keyword" do end end - it "converts the splatted list of exceptions using #to_a" do - exceptions = mock("to_a") - exceptions.should_receive(:to_a).and_return(exception_list) - caught_it = false - begin - raise SpecificExampleException, "not important" - rescue *exceptions - caught_it = true - end - caught_it.should be_true - end - it "can combine a splatted list of exceptions with a literal list of exceptions" do caught_it = false begin @@ -504,12 +492,14 @@ describe "The rescue keyword" do }.should raise_error(Exception) end - it "rescues with multiple assignment" do + ruby_version_is "2.7" do + it "rescues with multiple assignment" do - a, b = raise rescue [1, 2] + a, b = raise rescue [1, 2] - a.should == 1 - b.should == 2 + a.should == 1 + b.should == 2 + end end end end diff --git a/spec/ruby/language/return_spec.rb b/spec/ruby/language/return_spec.rb index a62ed1242d..d8506834c8 100644 --- a/spec/ruby/language/return_spec.rb +++ b/spec/ruby/language/return_spec.rb @@ -422,31 +422,18 @@ describe "The return keyword" do end describe "within a block within a class" do - it "is not allowed" do - File.write(@filename, <<-END_OF_CODE) - class ReturnSpecs::A - ScratchPad << "before return" - 1.times { return } - ScratchPad << "after return" - end - END_OF_CODE - - -> { load @filename }.should raise_error(LocalJumpError) - end - end - - describe "within BEGIN" do - it "is allowed" do - File.write(@filename, <<-END_OF_CODE) - BEGIN { - ScratchPad << "before call" - return - ScratchPad << "after call" - } - END_OF_CODE - - load @filename - ScratchPad.recorded.should == ["before call"] + ruby_version_is "2.7" do + it "is not allowed" do + File.write(@filename, <<-END_OF_CODE) + class ReturnSpecs::A + ScratchPad << "before return" + 1.times { return } + ScratchPad << "after return" + end + END_OF_CODE + + -> { load @filename }.should raise_error(LocalJumpError) + end end end @@ -477,13 +464,25 @@ describe "The return keyword" do end describe "return with argument" do - it "warns but does not affect exit status" do - err = ruby_exe(<<-END_OF_CODE, args: "2>&1") - return 10 - END_OF_CODE - $?.exitstatus.should == 0 + ruby_version_is ""..."2.7" do + it "does not affect exit status" do + ruby_exe(<<-END_OF_CODE).should == "" + return 10 + END_OF_CODE + + $?.exitstatus.should == 0 + end + end + + ruby_version_is "2.7" do + it "warns but does not affect exit status" do + err = ruby_exe(<<-END_OF_CODE, args: "2>&1") + return 10 + END_OF_CODE + $?.exitstatus.should == 0 - err.should =~ /warning: argument of top-level return is ignored/ + err.should =~ /warning: argument of top-level return is ignored/ + end end end end diff --git a/spec/ruby/language/safe_spec.rb b/spec/ruby/language/safe_spec.rb index 03ae96148e..89830a2069 100644 --- a/spec/ruby/language/safe_spec.rb +++ b/spec/ruby/language/safe_spec.rb @@ -1,11 +1,119 @@ require_relative '../spec_helper' describe "The $SAFE variable" do - it "$SAFE is a regular global variable" do - $SAFE.should == nil - $SAFE = 42 - $SAFE.should == 42 - ensure - $SAFE = nil + ruby_version_is ""..."2.7" do + after :each do + $SAFE = 0 + end + + it "is 0 by default" do + $SAFE.should == 0 + proc { + $SAFE.should == 0 + }.call + end + + it "can be set to 0" do + proc { + $SAFE = 0 + $SAFE.should == 0 + }.call + end + + it "can be set to 1" do + proc { + $SAFE = 1 + $SAFE.should == 1 + }.call + end + + [2, 3, 4].each do |n| + it "cannot be set to #{n}" do + -> { + proc { + $SAFE = n + }.call + }.should raise_error(ArgumentError, /\$SAFE=2 to 4 are obsolete/) + end + end + + it "raises ArgumentError when set to values below 0" do + -> { + proc { + $SAFE = -100 + }.call + }.should raise_error(ArgumentError, "$SAFE should be >= 0") + end + + it "cannot be set to values above 4" do + -> { + proc { + $SAFE = 100 + }.call + }.should raise_error(ArgumentError, /\$SAFE=2 to 4 are obsolete/) + end + + it "can be manually lowered" do + $SAFE = 1 + $SAFE = 0 + $SAFE.should == 0 + end + + it "is not Proc local" do + $SAFE.should == 0 + proc { + $SAFE = 1 + }.call + $SAFE.should == 1 + end + + it "is not lambda local" do + $SAFE.should == 0 + -> { + $SAFE = 1 + }.call + $SAFE.should == 1 + end + + it "is global like regular global variables" do + Thread.new { $SAFE }.value.should == 0 + $SAFE = 1 + Thread.new { $SAFE }.value.should == 1 + end + + it "can be read when default from Thread#safe_level" do + Thread.current.safe_level.should == 0 + end + + it "can be read when modified from Thread#safe_level" do + proc { + $SAFE = 1 + Thread.current.safe_level.should == 1 + }.call + end + end + + ruby_version_is "2.7"..."3.0" do + it "warn when access" do + -> { + $SAFE + }.should complain(/\$SAFE will become a normal global variable in Ruby 3.0/) + end + + it "warn when set" do + -> { + $SAFE = 1 + }.should complain(/\$SAFE will become a normal global variable in Ruby 3.0/) + end + end + + ruby_version_is "3.0" do + it "$SAFE is a regular global variable" do + $SAFE.should == nil + $SAFE = 42 + $SAFE.should == 42 + ensure + $SAFE = nil + end end end diff --git a/spec/ruby/language/send_spec.rb b/spec/ruby/language/send_spec.rb index a1656559fe..e57e2c65dc 100644 --- a/spec/ruby/language/send_spec.rb +++ b/spec/ruby/language/send_spec.rb @@ -258,10 +258,20 @@ describe "Invoking a private setter method" do end describe "Invoking a private getter method" do - it "permits self as a receiver" do - receiver = LangSendSpecs::PrivateGetter.new - receiver.call_self_foo_or_equals(6) - receiver.call_self_foo.should == 6 + 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 + + ruby_version_is "2.7" do + it "permits self as a receiver" do + receiver = LangSendSpecs::PrivateGetter.new + receiver.call_self_foo_or_equals(6) + receiver.call_self_foo.should == 6 + end end end @@ -411,18 +421,36 @@ describe "Invoking a method" do specs.rest_len(0,*a,4,*5,6,7,*c,-1).should == 11 end - it "expands the Array elements from the splat before applying block argument operations" do - def self.m(*args, &block) - [args, block] + ruby_version_is ""..."3.0" do + 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] end + end - args = [1, nil] - m(*args, &args.pop).should == [[1, nil], nil] + ruby_version_is "3.0" do + it "expands the Array elements from the splat before applying block argument operations" do + def self.m(*args, &block) + [args, block] + end - args = [1, nil] - order = [] - m(*(order << :args; args), &(order << :block; args.pop)).should == [[1, nil], nil] - order.should == [:args, :block] + 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 end it "evaluates the splatted arguments before the block if there are other arguments after the splat" do diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb index 9d037717b2..c1fb682ea0 100644 --- a/spec/ruby/language/singleton_class_spec.rb +++ b/spec/ruby/language/singleton_class_spec.rb @@ -291,27 +291,3 @@ describe "Instantiating a singleton class" do }.should raise_error(TypeError) end end - -describe "Frozen properties" do - it "is frozen if the object it is created from is frozen" do - o = Object.new - o.freeze - klass = o.singleton_class - klass.frozen?.should == true - end - - it "will be frozen if the object it is created from becomes frozen" do - o = Object.new - klass = o.singleton_class - klass.frozen?.should == false - o.freeze - klass.frozen?.should == true - end - - it "will be unfrozen if the frozen object is cloned with freeze set to false" do - o = Object.new - o.freeze - o2 = o.clone(freeze: false) - o2.singleton_class.frozen?.should == false - end -end diff --git a/spec/ruby/language/source_encoding_spec.rb b/spec/ruby/language/source_encoding_spec.rb index 7135bc0a70..19364fc676 100644 --- a/spec/ruby/language/source_encoding_spec.rb +++ b/spec/ruby/language/source_encoding_spec.rb @@ -15,7 +15,7 @@ describe "Source files" do end describe "encoded in UTF-16 LE without a BOM" do - it "are parsed as empty because they contain a NUL byte before the encoding comment" do + it "are parsed because empty as they contain a NUL byte before the encoding comment" do ruby_exe(fixture(__FILE__, "utf16-le-nobom.rb"), args: "2>&1").should == "" end end diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb index 418dc2ca7d..ce4941569e 100644 --- a/spec/ruby/language/string_spec.rb +++ b/spec/ruby/language/string_spec.rb @@ -56,6 +56,28 @@ describe "Ruby character strings" do "#\$".should == '#$' end + ruby_version_is ''...'2.7' do + it "taints the result of interpolation when an interpolated value is tainted" do + "#{"".taint}".tainted?.should be_true + + @ip.taint + "#@ip".tainted?.should be_true + + $ip.taint + "#$ip".tainted?.should be_true + end + + it "untrusts the result of interpolation when an interpolated value is untrusted" do + "#{"".untrust}".untrusted?.should be_true + + @ip.untrust + "#@ip".untrusted?.should be_true + + $ip.untrust + "#$ip".untrusted?.should be_true + end + end + it "allows using non-alnum characters as string delimiters" do %(hey #{@ip}).should == "hey xxx" %[hey #{@ip}].should == "hey xxx" @@ -277,11 +299,23 @@ describe "Ruby String interpolation" do eval(code).should_not.frozen? end - it "creates a non-frozen String when # frozen-string-literal: true is used" do - code = <<~'RUBY' - # frozen-string-literal: true - "a#{6*7}c" - RUBY - eval(code).should_not.frozen? + ruby_version_is "3.0" do + it "creates a non-frozen String when # frozen-string-literal: true is used" do + code = <<~'RUBY' + # frozen-string-literal: true + "a#{6*7}c" + RUBY + eval(code).should_not.frozen? + end + end + + ruby_version_is ""..."3.0" do + it "creates a frozen String when # frozen-string-literal: true is used" do + code = <<~'RUBY' + # frozen-string-literal: true + "a#{6*7}c" + RUBY + eval(code).should.frozen? + end end end diff --git a/spec/ruby/language/super_spec.rb b/spec/ruby/language/super_spec.rb index d22c603605..1ac5c5e1be 100644 --- a/spec/ruby/language/super_spec.rb +++ b/spec/ruby/language/super_spec.rb @@ -203,25 +203,6 @@ describe "The super keyword" do -> { klass.new.a(:a_called) }.should raise_error(RuntimeError) end - it "is able to navigate to super, when a method is defined dynamically on the singleton class" do - foo_class = Class.new do - def bar - "bar" - end - end - - mixin_module = Module.new do - def bar - "super_" + super - end - end - - foo = foo_class.new - foo.singleton_class.define_method(:bar, mixin_module.instance_method(:bar)) - - foo.bar.should == "super_bar" - end - # Rubinius ticket github#157 it "calls method_missing when a superclass method is not found" do SuperSpecs::MM_B.new.is_a?(Hash).should == false @@ -341,10 +322,6 @@ describe "The super keyword" do SuperSpecs::ZSuperWithUnderscores::B.new.m_modified(1, 2).should == [14, 2] end - it "should pass method arguments when called within a closure" do - SuperSpecs::ZSuperInBlock::B.new.m(arg: 1).should == 1 - end - describe 'when using keyword arguments' do before :each do @req = SuperSpecs::Keywords::RequiredArguments.new diff --git a/spec/ruby/language/symbol_spec.rb b/spec/ruby/language/symbol_spec.rb index 7c1898efc2..d6a41d3059 100644 --- a/spec/ruby/language/symbol_spec.rb +++ b/spec/ruby/language/symbol_spec.rb @@ -96,11 +96,11 @@ describe "A Symbol literal" do %I{a b #{"c"}}.should == [:a, :b, :c] end - it "raises an EncodingError at parse time when Symbol with invalid bytes" do + it "with invalid bytes raises an EncodingError at parse time" do ScratchPad.record [] -> { eval 'ScratchPad << 1; :"\xC3"' - }.should raise_error(EncodingError, 'invalid symbol in encoding UTF-8 :"\xC3"') + }.should raise_error(EncodingError, /invalid/) ScratchPad.recorded.should == [] end end diff --git a/spec/ruby/language/undef_spec.rb b/spec/ruby/language/undef_spec.rb index 29dba4afb4..4e473b803f 100644 --- a/spec/ruby/language/undef_spec.rb +++ b/spec/ruby/language/undef_spec.rb @@ -38,19 +38,12 @@ describe "The undef keyword" do -> { @obj.meth(5) }.should raise_error(NoMethodError) end - it "with an interpolated symbol" do + it "with a interpolated symbol" do @undef_class.class_eval do undef :"#{'meth'}" end -> { @obj.meth(5) }.should raise_error(NoMethodError) end - - it "with an interpolated symbol when interpolated expression is not a String literal" do - @undef_class.class_eval do - undef :"#{'meth'.to_sym}" - end - -> { @obj.meth(5) }.should raise_error(NoMethodError) - end end it "allows undefining multiple methods at a time" do diff --git a/spec/ruby/language/variables_spec.rb b/spec/ruby/language/variables_spec.rb index 23c2cdb557..699187335c 100644 --- a/spec/ruby/language/variables_spec.rb +++ b/spec/ruby/language/variables_spec.rb @@ -1,86 +1,6 @@ require_relative '../spec_helper' require_relative 'fixtures/variables' -describe "Evaluation order during assignment" do - context "with single assignment" do - it "evaluates from left to right" do - obj = VariablesSpecs::EvalOrder.new - obj.instance_eval do - foo[0] = a - end - - obj.order.should == ["foo", "a", "foo[]="] - end - end - - context "with multiple assignment" do - ruby_version_is ""..."3.1" do - it "does not evaluate from left to right" do - obj = VariablesSpecs::EvalOrder.new - - obj.instance_eval do - foo[0], bar.baz = a, b - end - - obj.order.should == ["a", "b", "foo", "foo[]=", "bar", "bar.baz="] - end - - it "cannot be used to swap variables with nested method calls" do - node = VariablesSpecs::EvalOrder.new.node - - original_node = node - original_node_left = node.left - original_node_left_right = node.left.right - - node.left, node.left.right, node = node.left.right, node, node.left - # Should evaluate in the order of: - # RHS: node.left.right, node, node.left - # LHS: - # * node(original_node), original_node.left = original_node_left_right - # * node(original_node), node.left(changed in the previous assignment to original_node_left_right), - # original_node_left_right.right = original_node - # * node = original_node_left - - node.should == original_node_left - node.right.should_not == original_node - node.right.left.should_not == original_node_left_right - end - end - - ruby_version_is "3.1" do - it "evaluates from left to right, receivers first then methods" do - obj = VariablesSpecs::EvalOrder.new - obj.instance_eval do - foo[0], bar.baz = a, b - end - - obj.order.should == ["foo", "bar", "a", "b", "foo[]=", "bar.baz="] - end - - it "can be used to swap variables with nested method calls" do - node = VariablesSpecs::EvalOrder.new.node - - original_node = node - original_node_left = node.left - original_node_left_right = node.left.right - - node.left, node.left.right, node = node.left.right, node, node.left - # Should evaluate in the order of: - # LHS: node, node.left(original_node_left) - # RHS: original_node_left_right, original_node, original_node_left - # Ops: - # * node(original_node), original_node.left = original_node_left_right - # * original_node_left.right = original_node - # * node = original_node_left - - node.should == original_node_left - node.right.should == original_node - node.right.left.should == original_node_left_right - end - end - end -end - describe "Multiple assignment" do context "with a single RHS value" do it "assigns a simple MLHS" do @@ -877,6 +797,17 @@ describe 'Local variable shadowing' do end describe 'Allowed characters' do + # new feature in 2.6 -- https://bugs.ruby-lang.org/issues/13770 + it 'does not allow non-ASCII upcased characters at the beginning' do + -> do + eval <<-CODE + def test + ἍBB = 1 + end + CODE + end.should raise_error(SyntaxError, /dynamic constant assignment/) + end + it 'allows non-ASCII lowercased characters at the beginning' do result = nil @@ -890,50 +821,33 @@ describe 'Allowed characters' do result.should == 1 end - - it 'parses a non-ASCII upcased character as a constant identifier' do - -> do - eval <<-CODE - def test - ἍBB = 1 - end - CODE - end.should raise_error(SyntaxError, /dynamic constant assignment/) - end end describe "Instance variables" do context "when instance variable is uninitialized" do - it "doesn't warn about accessing uninitialized instance variable" do - obj = Object.new - def obj.foobar; a = @a; end - - -> { obj.foobar }.should_not complain(verbose: true) - end - - it "doesn't warn at lazy initialization" do - obj = Object.new - def obj.foobar; @a ||= 42; end - - -> { obj.foobar }.should_not complain(verbose: true) - end - end - - describe "global variable" do - context "when global variable is uninitialized" do - it "warns about accessing uninitialized global variable in verbose mode" do + ruby_version_is ""..."3.0" do + it "warns about accessing uninitialized instance variable" do obj = Object.new - def obj.foobar; a = $specs_uninitialized_global_variable; end + def obj.foobar; a = @a; end - -> { obj.foobar }.should complain(/warning: global variable `\$specs_uninitialized_global_variable' not initialized/, verbose: true) + -> { obj.foobar }.should complain(/warning: instance variable @a not initialized/, verbose: true) end + end - it "doesn't warn at lazy initialization" do + ruby_version_is "3.0" do + it "doesn't warn about accessing uninitialized instance variable" do obj = Object.new - def obj.foobar; $specs_uninitialized_global_variable_lazy ||= 42; end + def obj.foobar; a = @a; end -> { obj.foobar }.should_not complain(verbose: true) end end + + it "doesn't warn at lazy initialization" do + obj = Object.new + def obj.foobar; @a ||= 42; end + + -> { obj.foobar }.should_not complain(verbose: true) + end end end diff --git a/spec/ruby/language/yield_spec.rb b/spec/ruby/language/yield_spec.rb index 5283517636..3db6d353a9 100644 --- a/spec/ruby/language/yield_spec.rb +++ b/spec/ruby/language/yield_spec.rb @@ -186,23 +186,30 @@ describe "The yield call" do end describe "Using yield in a singleton class literal" do - it 'raises a SyntaxError' do - code = <<~RUBY - class << Object.new - yield - end - RUBY + ruby_version_is "2.7"..."3.0" do + it 'emits a deprecation warning' do + code = <<~RUBY + def m + class << Object.new + yield + end + end + m { :ok } + RUBY - -> { eval(code) }.should raise_error(SyntaxError, /Invalid yield/) + -> { eval(code) }.should complain(/warning: `yield' in class syntax will not be supported from Ruby 3.0/) + end end -end -describe "Using yield in non-lambda block" do - it 'raises a SyntaxError' do - code = <<~RUBY - 1.times { yield } + ruby_version_is "3.0" do + it 'raises a SyntaxError' do + code = <<~RUBY + class << Object.new + yield + end RUBY - -> { eval(code) }.should raise_error(SyntaxError, /Invalid yield/) + -> { eval(code) }.should raise_error(SyntaxError, /Invalid yield/) + end end end |
