diff options
author | Benoit Daloze <eregontp@gmail.com> | 2022-04-25 14:53:54 +0200 |
---|---|---|
committer | Benoit Daloze <eregontp@gmail.com> | 2022-04-25 14:53:54 +0200 |
commit | 45cf4f218728a15eb36d14a6c9912086525f5e3f (patch) | |
tree | 2aa93fadcb904c226f722dde47827098b87a9846 /spec/ruby/language | |
parent | 6ae81d49b52563a6720d666a6118ffa6e484f398 (diff) | |
download | ruby-45cf4f218728a15eb36d14a6c9912086525f5e3f.tar.gz |
Update to ruby/spec@3affe1e
Diffstat (limited to 'spec/ruby/language')
24 files changed, 1470 insertions, 1844 deletions
diff --git a/spec/ruby/language/alias_spec.rb b/spec/ruby/language/alias_spec.rb index d1d06e3fac..c353390679 100644 --- a/spec/ruby/language/alias_spec.rb +++ b/spec/ruby/language/alias_spec.rb @@ -243,6 +243,19 @@ 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 f213d68ba1..42652152a1 100644 --- a/spec/ruby/language/block_spec.rb +++ b/spec/ruby/language/block_spec.rb @@ -101,17 +101,7 @@ describe "A block yielded a single" do end end - 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 - - ruby_version_is "2.7"...'3.0' do + ruby_version_is ""...'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}) @@ -958,38 +948,18 @@ describe "Post-args" do end describe "with a circular argument reference" do - 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 + 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 - 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 + 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 it "calls an existing method with the same name as the argument if explicitly using ()" do diff --git a/spec/ruby/language/comment_spec.rb b/spec/ruby/language/comment_spec.rb index 690e844dc0..dd788e681c 100644 --- a/spec/ruby/language/comment_spec.rb +++ b/spec/ruby/language/comment_spec.rb @@ -1,15 +1,13 @@ require_relative '../spec_helper' describe "The comment" do - ruby_version_is "2.7" do - it "can be placed between fluent dot now" do - code = <<~CODE - 10 - # some comment - .to_s - CODE + it "can be placed between fluent dot now" do + code = <<~CODE + 10 + # some comment + .to_s + CODE - eval(code).should == '10' - end + eval(code).should == '10' end end diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb index d72f8fa888..c8531343c0 100644 --- a/spec/ruby/language/def_spec.rb +++ b/spec/ruby/language/def_spec.rb @@ -197,32 +197,15 @@ describe "An instance method with a default argument" do foo(2,3,3).should == [2,3,[3]] end - 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 when there is 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) + it "raises a SyntaxError when there is 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 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 38345c3727..ae2bf45bda 100644 --- a/spec/ruby/language/defined_spec.rb +++ b/spec/ruby/language/defined_spec.rb @@ -48,6 +48,20 @@ 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) diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb index 8e4183cbcc..3f24a79d5c 100644 --- a/spec/ruby/language/delegation_spec.rb +++ b/spec/ruby/language/delegation_spec.rb @@ -1,42 +1,40 @@ require_relative '../spec_helper' require_relative 'fixtures/delegation' -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 +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 + 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 + it "delegates block" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate_block(...) + target_block(...) + end + RUBY - a.new.delegate_block(1, b: 2) { |x| x }.should == [{b: 2}, [1]] - end + a.new.delegate_block(1, b: 2) { |x| x }.should == [{b: 2}, [1]] + end - 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 + 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 + a.new.delegate(1, b: 2).should == Range.new([[], {}], nil, true) end end @@ -63,6 +61,5 @@ ruby_version_is "2.7.3" do a.new.delegate_block(0, 1, b: 2) { |x| x }.should == [{b: 2}, [1]] end - end end diff --git a/spec/ruby/language/fixtures/defined.rb b/spec/ruby/language/fixtures/defined.rb index 8b6004c19f..a9552619bf 100644 --- a/spec/ruby/language/fixtures/defined.rb +++ b/spec/ruby/language/fixtures/defined.rb @@ -19,6 +19,9 @@ module DefinedSpecs DefinedSpecs end + def self.any_args(*) + end + class Basic A = 42 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 a4d655ad02..cccc5969bd 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".object_id == "abc".object_id +p "abc".equal?("abc") diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb index 2f8b97199a..c84564d3ea 100644 --- a/spec/ruby/language/hash_spec.rb +++ b/spec/ruby/language/hash_spec.rb @@ -148,18 +148,9 @@ describe "Hash literal" do {a: 1, **obj, c: 3}.should == {a:1, b: 2, c: 3, d: 4} end - 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 + 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 it "raises a TypeError if #to_hash does not return a Hash" do diff --git a/spec/ruby/language/heredoc_spec.rb b/spec/ruby/language/heredoc_spec.rb index 61a27ad8e0..b3b160fd11 100644 --- a/spec/ruby/language/heredoc_spec.rb +++ b/spec/ruby/language/heredoc_spec.rb @@ -59,20 +59,10 @@ HERE s.encoding.should == Encoding::US_ASCII end - 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 + 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 it "allows HEREDOC with <<~'identifier', allowing to indent identifier and content" do diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb index 66e37915a2..0c72f59d38 100644 --- a/spec/ruby/language/keyword_arguments_spec.rb +++ b/spec/ruby/language/keyword_arguments_spec.rb @@ -302,6 +302,63 @@ ruby_version_is "3.0" do m(a: 1).should == [[{a: 1}], {}] m({a: 1}).should == [[{a: 1}], {}] 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 end diff --git a/spec/ruby/language/lambda_spec.rb b/spec/ruby/language/lambda_spec.rb index 6393fb5c47..a3f01ec04b 100644 --- a/spec/ruby/language/lambda_spec.rb +++ b/spec/ruby/language/lambda_spec.rb @@ -281,38 +281,18 @@ describe "A lambda literal -> () { }" do end describe "with circular optional argument reference" do - 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 + 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 - 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 + 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 it "calls an existing method with the same name as the argument if explicitly using ()" do @@ -360,26 +340,12 @@ describe "A lambda expression 'lambda { ... }'" do def meth; lambda; end end - ruby_version_is ""..."2.7" do - it "can be created" do - implicit_lambda = nil + it "raises ArgumentError" do + implicit_lambda = nil + suppress_warning do -> { - 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 + meth { 1 } + }.should raise_error(ArgumentError, /tried to create Proc object without a block/) end end end diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index 0a5bb99d0b..d464e79403 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -602,13 +602,11 @@ describe "A method" do -> { m(2) }.should raise_error(ArgumentError) end - ruby_version_is "2.7" do - evaluate <<-ruby do - def m(**k); k end; - ruby + evaluate <<-ruby do + def m(**k); k end; + ruby - m("a" => 1).should == { "a" => 1 } - end + m("a" => 1).should == { "a" => 1 } end evaluate <<-ruby do @@ -962,26 +960,13 @@ describe "A method" do 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 == {"a" => 1} - end - end - - ruby_version_is "2.7" do - evaluate <<-ruby do - def m(a=1, **) a end - ruby + 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 + m().should == 1 + m(2, a: 1, b: 0).should == 2 + m("a" => 1, a: 2).should == 1 end evaluate <<-ruby do @@ -1021,164 +1006,7 @@ 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 + ruby_version_is ""...'3.0' do evaluate <<-ruby do def m(*, a:) a end ruby @@ -1503,44 +1331,22 @@ describe "A method" do end end - 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 raise_error(ArgumentError) - end - - evaluate <<-ruby do - def m(a:, **k) [a, k] end - ruby + evaluate <<-ruby do + def m(a:, **) a 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 + m(a: 1).should == 1 + m(a: 1, b: 2).should == 1 + m("a" => 1, a: 1, b: 2).should == 1 end - 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 - - evaluate <<-ruby do - def m(a:, **k) [a, k] end - ruby + 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 + m(a: 1).should == [1, {}] + m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}] + m("a" => 1, a: 1, b: 2).should == [1, {"a" => 1, b: 2}] end evaluate <<-ruby do @@ -1637,18 +1443,16 @@ describe "A method" do result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] end - ruby_version_is "2.7" do - evaluate <<-ruby do - def m(a, **nil); a end; - ruby + 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) - -> { m(**{a: 1}) }.should raise_error(ArgumentError) - -> { m("a" => 1) }.should raise_error(ArgumentError) - end + -> { m(a: 1) }.should raise_error(ArgumentError) + -> { m(**{a: 1}) }.should raise_error(ArgumentError) + -> { m("a" => 1) }.should raise_error(ArgumentError) end ruby_version_is ''...'3.0' do @@ -1690,19 +1494,17 @@ describe "A method" do end end - 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 + context 'when passing an empty keyword splat to a method that does not accept keywords' do + evaluate <<-ruby do + def m(*a); a; end + ruby - h = {} - m(**h).should == [] - end + h = {} + m(**h).should == [] end end - ruby_version_is '2.7'...'3.0' do + 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 diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb index 838822b2d6..424d7a06e3 100644 --- a/spec/ruby/language/numbered_parameters_spec.rb +++ b/spec/ruby/language/numbered_parameters_spec.rb @@ -1,120 +1,118 @@ require_relative '../spec_helper' -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 +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 + 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 "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 "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 "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 "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 "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 "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 - 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 + ruby_version_is ''...'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 + 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 - 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 + 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 + 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 - 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/) + 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("proc { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("proc { |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("lambda { || _1 }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) - -> { eval("lambda { |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("['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("['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 - 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 + describe "assigning to a numbered parameter" do + ruby_version_is ''...'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 + 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 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 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/) - end + -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/) end end diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb index c68b6caa34..f3cc86fa0b 100644 --- a/spec/ruby/language/pattern_matching_spec.rb +++ b/spec/ruby/language/pattern_matching_spec.rb @@ -1,240 +1,356 @@ require_relative '../spec_helper' -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 +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. - before :each do - ScratchPad.record [] + before :each do + ScratchPad.record [] + end + + 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] + [a, b] + RUBY + end end - 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] - [a, b] - RUBY - 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 - 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 - - 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 - - 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 - end - RUBY - 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 - 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 - 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 + + 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 + + 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 + end + RUBY + 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 + 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 + end + RUBY end 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 + 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 - it "allows using then operator" do - eval(<<~RUBY).should == :bar - case [0, 1] - in [0] then :foo - in [0, 1] then :bar - end - RUBY + after :each do + Warning[:experimental] = @experimental end - describe "warning" do + context 'when regular form' do before :each do - @experimental, Warning[:experimental] = Warning[:experimental], true + @src = 'case [0, 1]; in [a, b]; end' + end + + 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 - after :each do - Warning[:experimental] = @experimental + ruby_version_is "3.0" do + it "does not warn about pattern matching is experimental feature" do + -> { eval @src }.should_not complain + end end + end - context 'when regular form' do + context 'when one-line form' do + ruby_version_is '3.0' do before :each do - @src = 'case [0, 1]; in [a, b]; end' + @src = '[0, 1] => [a, b]' end - ruby_version_is ""..."3.0" do + 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 - ruby_version_is "3.0" do + 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 + end - context 'when one-line form' do - ruby_version_is '3.0' do - before :each do - @src = '[0, 1] => [a, b]' - end + it "binds variables" do + eval(<<~RUBY).should == 1 + case [0, 1] + in [0, a] + a + end + RUBY + 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 "cannot mix in and when operators" do + -> { + eval <<~RUBY + case [] + when 1 == 1 + in [] + end + RUBY + }.should raise_error(SyntaxError, /syntax error, unexpected `in'/) - ruby_version_is "3.1" do - it "does not warn about pattern matching is experimental feature" do - -> { eval @src }.should_not complain - end - end + -> { + eval <<~RUBY + case [] + in [] + when 1 == 1 end + RUBY + }.should raise_error(SyntaxError, /syntax error, unexpected `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 + + 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 "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 "binds variables" do - eval(<<~RUBY).should == 1 + 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 case [0, 1] - in [0, a] - a + in [a, 1] if a >= 0 + true end RUBY end - 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'/) + it "does not evaluate guard if pattern does not match" do + eval <<~RUBY + case 0 + in 1 if (ScratchPad << :foo) || true + else + end + RUBY - -> { - eval <<~RUBY - case [] - in [] - when 1 == 1 - end - RUBY - }.should raise_error(SyntaxError, /syntax error, unexpected `when'/) + ScratchPad.recorded.should == [] end - it "checks patterns until the first matching" do + it "takes guards into account when there are several matching patterns" do eval(<<~RUBY).should == :bar - case [0, 1] - in [0] + case 0 + in 0 if false :foo - in [0, 1] + in 0 if true :bar - in [0, 1] - :baz end RUBY end - it "executes else clause if no pattern matches" do + it "executes else clause if no guarded pattern matches" do eval(<<~RUBY).should == false - case [0, 1] - in [0] + case 0 + in 0 if false true else false @@ -242,1164 +358,1046 @@ ruby_version_is "2.7" do RUBY end - it "raises NoMatchingPatternError if no pattern matches and no else clause" do + it "raises NoMatchingPatternError if no guarded pattern matches and no else clause" do -> { eval <<~RUBY case [0, 1] - in [0] + in [0, 1] if false end RUBY }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) end + end - 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 "evaluates the case expression once for multiple patterns, caching the result" do + describe "value pattern" do + it "matches an object such that pattern === object" do eval(<<~RUBY).should == true - case (ScratchPad << :foo; 1) + case 0 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 - case [0, 1] - in [a, 1] if a >= 0 - true - 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 - - ScratchPad.recorded.should == [] - end + eval(<<~RUBY).should == true + case 0 + in (-1..1) + true + end + RUBY - 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 + eval(<<~RUBY).should == true + case 0 + in Integer + true + end + RUBY - 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 - end + eval(<<~RUBY).should == true + case "0" + in /0/ + 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 + eval(<<~RUBY).should == true + case "0" + in ->(s) { s == "0" } + true + end + RUBY 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 + it "allows string literal with interpolation" do + x = "x" - 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 "x" + in "#{x + ""}" + true + end + RUBY + end + end - eval(<<~RUBY).should == true - case "0" - in ->(s) { s == "0" } - 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 "allows string literal with interpolation" do - x = "x" + it "makes bounded variable visible outside a case statement scope" do + eval(<<~RUBY).should == 0 + case 0 + in a + end - eval(<<~RUBY).should == true - case "x" - in "#{x + ""}" - true - end - RUBY - end + a + 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 "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 "makes bounded variable visible outside a case statement scope" do - eval(<<~RUBY).should == 0 - case 0 - in a - end + [a, b, c] + RUBY + end + it "allow using _ name to drop values" do + eval(<<~RUBY).should == 0 + case [0, 1] + in [a, _] a - 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 - - [a, b, c] - RUBY - end + 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 "supports using _ in a pattern several times" do + eval(<<~RUBY).should == true + case [0, 1, 2] + in [0, _, _] + true + 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 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 - 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 {a: 0, b: 1, c: 2} - in {a: 0, b: _x, c: _x} - true + it "does not support using variable name (except _) several times" do + -> { + eval <<~RUBY + case [0] + in [a, a] end RUBY - end + }.should raise_error(SyntaxError, /duplicated variable name/) + end - 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 + it "supports existing variables in a pattern specified with ^ operator" do + a = 0 - 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 - 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 - it "allows applying ^ operator to bound variables" do - eval(<<~RUBY).should == 1 - case [1, 1] - in [n, ^n] - n - end - RUBY + eval(<<~RUBY).should == false + case [1, 2] + in [n, ^n] + true + else + false + end + RUBY + end - eval(<<~RUBY).should == false + 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] + in [^n, n] true else false 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] - true - else - false - end - RUBY - }.should raise_error(SyntaxError, /n: no such local variable/) - end + }.should raise_error(SyntaxError, /n: no such local variable/) 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 - 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 + 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 - it "support underscore prefixed variables in alternation" do - eval(<<~RUBY).should == true + it "does not support variable binding" do + -> { + eval <<~RUBY case [0, 1] - in [1, _] - false - in [0, 0] | [0, _a] - true + in [0, 0] | [0, a] 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"]] - true - 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 + }.should raise_error(SyntaxError, /illegal variable in alternative pattern/) end - 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 - - 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 + 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 - describe "Array pattern" do - it "supports form Constant(pat, pat, ...)" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in Array(0, 1, 2) + 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 - end + end + RUBY - it "supports form Constant[pat, pat, ...]" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in Array[0, 1, 2] + eval(<<~RUBY).should == true + case [1, 2] + in [0, _] | {a: 0} + false + in {a: 1, b: 2} | [1, 2] true - end - RUBY - end + 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 "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 - it "supports form pat, pat, ..." do - eval(<<~RUBY).should == true - case [0, 1, 2] - in 0, 1, 2 - 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 == 1 - case [0, 1, 2] - in 0, a, 2 - a - end - RUBY + 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 == [1, 2] - case [0, 1, 2] - in 0, *rest - rest - 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 "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 "supports form [pat, pat, ...]" do + eval(<<~RUBY).should == true + case [0, 1, 2] + in [0, 1, 2] + true + end + RUBY + end - eval(<<~RUBY).should == true - case obj - in [Integer, Integer] - true - end - RUBY - end + it "supports form pat, pat, ..." do + eval(<<~RUBY).should == true + case [0, 1, 2] + in 0, 1, 2 + true + end + RUBY - ruby_version_is "3.0" do - it "calls #deconstruct once for multiple patterns, caching the result" do - obj = Object.new + eval(<<~RUBY).should == 1 + case [0, 1, 2] + in 0, a, 2 + a + end + RUBY - def obj.deconstruct - ScratchPad << :deconstruct - [0, 1] - end + eval(<<~RUBY).should == [1, 2] + case [0, 1, 2] + in 0, *rest + rest + end + RUBY + end - eval(<<~RUBY).should == true - case obj - in [1, 2] - false - in [0, 1] - true - end - RUBY + 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 - ScratchPad.recorded.should == [:deconstruct] + eval(<<~RUBY).should == true + case obj + in [Integer, Integer] + true end - end + RUBY + end + + ruby_version_is "3.0" do + it "calls #deconstruct once for multiple patterns, caching the result" do + obj = Object.new - it "calls #deconstruct even on objects that are already an array" do - obj = [1, 2] def obj.deconstruct ScratchPad << :deconstruct - [3, 4] + [0, 1] end eval(<<~RUBY).should == true case obj - in [3, 4] - true - else + in [1, 2] false + in [0, 1] + true end RUBY ScratchPad.recorded.should == [:deconstruct] end + 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 "calls #deconstruct even on objects that are already an array" do + obj = [1, 2] + def obj.deconstruct + ScratchPad << :deconstruct + [3, 4] 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 + eval(<<~RUBY).should == true + case obj + in [3, 4] + true + else + false + end + RUBY - 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 + ScratchPad.recorded.should == [:deconstruct] + 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 + 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 + end - eval(<<~RUBY).should == true - case obj - in [1, 2] - false - in [0, 1] - true - end - RUBY - end + it "does not match object without #deconstruct method" do + obj = Object.new + obj.should_receive(:respond_to?).with(:deconstruct) - 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 + 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).should == false + -> { + eval <<~RUBY case obj - in Object[0] - true + in Object[] else - false end RUBY - end + }.should raise_error(TypeError, /deconstruct must return Array/) + end - it "binds variables" do - eval(<<~RUBY).should == [0, 1, 2] - case [0, 1, 2] - in [a, b, c] - [a, b, c] - end - RUBY + 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 - 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 == true + case obj + in [1, 2] + false + in [0, 1] + true + end + RUBY + end - 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 + 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 "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 == false + case obj + in Object[0] + true + else + false + end + RUBY + end - eval(<<~RUBY).should == true - case [0, 1, 2, 3] - in 0, 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 "matches [] with []" do - eval(<<~RUBY).should == true - case [] - in [] - 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 "matches anything with *" do - eval(<<~RUBY).should == true - case [0, 1] - in *; - true - end - RUBY - end + 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 - 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 + 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 == true - case [1, 2] - in [0, _] | {a: 0} - false - in {a: 1, b: 2} | [1, 2] - true - end - RUBY - end + eval(<<~RUBY).should == true + case [0, 1, 2, 3] + in 0, 1,; + true + 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 "matches [] with []" do + eval(<<~RUBY).should == true + case [] + in [] + 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 "matches anything with *" do + eval(<<~RUBY).should == true + case [0, 1] + in *; + true + 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} + 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 - end + end + RUBY - it "supports form id: pat, id: pat, ..." do - eval(<<~RUBY).should == true - case {a: 0, b: 1} - in a: 0, b: 1 + eval(<<~RUBY).should == true + case [1, 2] + in [0, _] | {a: 0} + false + in {a: 1, b: 2} | [1, 2] true - end - RUBY - - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in a: a, b: b - [a, b] - end - RUBY - - eval(<<~RUBY).should == { b: 1, c: 2 } - case {a: 0, b: 1, c: 2} - in a: 0, **rest - rest - end - RUBY - 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 - - a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in Hash[a:, b:] - [a, b] - end - RUBY + end + RUBY + end + end - a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in {a:, b:} - [a, b] - end - RUBY + 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 - 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 "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 - a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in a:, b: - [a, b] - 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 "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 + 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 'string': key literal" do - eval(<<~RUBY).should == true - case {a: 0} - in {"a": 0} - true - 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 support non-symbol keys" do - -> { - eval <<~RUBY - case {a: 1} - in {"a" => 1} - end - RUBY - }.should raise_error(SyntaxError, /unexpected/) - 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 not support string interpolation in keys" do - x = "a" + 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' - case {a: 1} - in {"#{x}": 1} - end - RUBY - }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed/) - end + a = b = nil + eval(<<~RUBY).should == [0, 1] + case {a: 0, b: 1} + in Hash[a:, b:] + [a, b] + end + RUBY - 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 + a = b = nil + eval(<<~RUBY).should == [0, 1] + case {a: 0, b: 1} + in {a:, b:} + [a, b] + end + 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 + a = nil + eval(<<~RUBY).should == [0, {b: 1, c: 2}] + case {a: 0, b: 1, c: 2} + in {a:, **rest} + [a, rest] + 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 a:, b: + [a, b] + end + RUBY + end - it "calls #deconstruct_keys per pattern" do - obj = Object.new + 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 - def obj.deconstruct_keys(*) - ScratchPad << :deconstruct_keys - {a: 1} + it "supports 'string': key literal" do + eval(<<~RUBY).should == true + case {a: 0} + in {"a": 0} + true end + RUBY + end - eval(<<~RUBY).should == true - case obj - in {b: 1} - false - in {a: 1} - true + it "does not support non-symbol keys" do + -> { + eval <<~RUBY + case {a: 1} + in {"a" => 1} end RUBY + }.should raise_error(SyntaxError, /unexpected/) + end - ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys] - end + it "does not support string interpolation in keys" do + x = "a" - it "does not match object if Constant === object returns false" do - eval(<<~RUBY).should == false + -> { + eval <<~'RUBY' case {a: 1} - in String[a: 1] - true - else - false + in {"#{x}": 1} end RUBY - end - - it "does not match object without #deconstruct_keys method" do - obj = Object.new - obj.should_receive(:respond_to?).with(:deconstruct_keys) + }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed/) + end - eval(<<~RUBY).should == false - case obj - in Object[a: 1] - true - else - false + it "raise SyntaxError when keys duplicate in pattern" do + -> { + eval <<~RUBY + case {a: 1} + in {a: 1, b: 2, a: 3} end RUBY - end - - 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 + }.should raise_error(SyntaxError, /duplicated key name/) + 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 + 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 == false - case obj - in Object[a: 1] - true - else - false - end - RUBY - end + eval(<<~RUBY).should == true + case obj + in {a: 1} + true + end + RUBY + 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 - end - RUBY + def obj.deconstruct_keys(*) + ScratchPad << :deconstruct_keys + {a: 1} end - it "passes keys specified in pattern as arguments to #deconstruct_keys method" do - obj = Object.new - - def obj.deconstruct_keys(*args) - ScratchPad << args - {a: 1, b: 2, c: 3} + eval(<<~RUBY).should == true + case obj + in {b: 1} + false + in {a: 1} + true end + RUBY - eval <<~RUBY - case obj - in Object[a: 1, b: 2, c: 3] - end - RUBY + ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys] + end - ScratchPad.recorded.sort.should == [[[:a, :b, :c]]] - 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 "passes keys specified in pattern to #deconstruct_keys method if pattern contains double splat operator **" do - obj = Object.new + it "does not match object without #deconstruct_keys method" do + obj = Object.new + obj.should_receive(:respond_to?).with(:deconstruct_keys) - def obj.deconstruct_keys(*args) - ScratchPad << args - {a: 1, b: 2, c: 3} + eval(<<~RUBY).should == false + case obj + in Object[a: 1] + true + else + false end + RUBY + end + 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, b: 2, **] + in Object[a: 1] end RUBY + }.should raise_error(TypeError, /deconstruct_keys must return Hash/) + end - ScratchPad.recorded.sort.should == [[[:a, :b]]] - 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 #deconstruct_keys method returns Hash with non-symbol keys" 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: 1] + true + else + false end + RUBY + end - eval <<~RUBY - case obj - in Object[a: 1, **rest] - end - RUBY + 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 - ScratchPad.recorded.should == [[nil]] - end + eval(<<~RUBY).should == false + case obj + in Object[a: 2] + true + else + false + end + RUBY + 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] - end - RUBY - end + it "passes keys specified in pattern as arguments to #deconstruct_keys method" do + obj = Object.new - 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 + def obj.deconstruct_keys(*args) + ScratchPad << args + {a: 1, b: 2, c: 3} 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 + eval <<~RUBY + case obj + in Object[a: 1, b: 2, c: 3] + end + RUBY - eval(<<~RUBY).should == false - case {a: 1, b: 2} - in {a: 1, **nil} - true - else - false - end - RUBY - end + ScratchPad.recorded.sort.should == [[[:a, :b, :c]]] + end - it "can match partially" do - eval(<<~RUBY).should == true - case {a: 1, b: 2} - in {a: 1} - true - end - RUBY - end + it "passes keys specified in pattern to #deconstruct_keys method if pattern contains double splat operator **" do + obj = Object.new - it "matches {} with {}" do - eval(<<~RUBY).should == true - case {} - in {} - true - end - RUBY + def obj.deconstruct_keys(*args) + ScratchPad << args + {a: 1, b: 2, c: 3} end - it "matches anything with **" do - eval(<<~RUBY).should == true - case {a: 1} - in **; - true - end - RUBY - end + eval <<~RUBY + case obj + in Object[a: 1, b: 2, **] + end + RUBY - 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 + ScratchPad.recorded.sort.should == [[[:a, :b]]] + end - 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 + it "passes nil to #deconstruct_keys method if pattern contains double splat operator **rest" do + obj = Object.new + + def obj.deconstruct_keys(*args) + ScratchPad << args + {a: 1, b: 2} end - end - describe "refinements" do - it "are used for #deconstruct" do - refinery = Module.new do - refine Array do - def deconstruct - [0] - end - end + eval <<~RUBY + case obj + in Object[a: 1, **rest] end + RUBY - result = nil - Module.new do - using refinery + ScratchPad.recorded.should == [[nil]] + end - result = eval(<<~RUBY) - case [] - in [0] - true - end - RUBY + 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.should == true - end - - it "are used for #deconstruct_keys" do - refinery = Module.new do - refine Hash do - def deconstruct_keys(_) - {a: 0} - end - 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 - result = nil - Module.new do - using refinery + 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 - result = eval(<<~RUBY) - case {} - in a: 0 - true - end - RUBY + eval(<<~RUBY).should == false + case {a: 1, b: 2} + in {a: 1, **nil} + true + else + false end + RUBY + end - result.should == true - end + it "can match partially" do + eval(<<~RUBY).should == true + case {a: 1, b: 2} + in {a: 1} + true + end + RUBY + end - 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 + it "matches {} with {}" do + eval(<<~RUBY).should == true + case {} + in {} + true end + RUBY + end - result = nil - Module.new do - using refinery + it "matches anything with **" do + eval(<<~RUBY).should == true + case {a: 1} + in **; + true + end + RUBY + end - result = eval(<<~RUBY) - case {} - in Array - true - end - RUBY + 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.should == true - end + 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 + 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 + describe "refinements" do + it "are used for #deconstruct" do + refinery = Module.new do + refine Array do + def deconstruct + [0] + end + end end - it "supports pinning instance variables" do - eval(<<~RUBY).should == true - @a = /a/ - case 'abc' - in ^@a + result = nil + Module.new do + using refinery + + result = eval(<<~RUBY) + case [] + in [0] true 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 - end - RUBY + it "are used for #deconstruct_keys" do + refinery = Module.new do + refine Hash do + def deconstruct_keys(_) + {a: 0} + end end - - result.should == true end - it "supports pinning global variables" do - eval(<<~RUBY).should == true - $a = /a/ - case 'abc' - in ^$a + result = nil + Module.new do + using refinery + + result = eval(<<~RUBY) + case {} + in a: 0 true end RUBY end - it "supports pinning expressions" do - eval(<<~RUBY).should == true - case 'abc' - in ^(/a/) - true + 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) end - RUBY + end + 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))} + result = nil + Module.new do + using refinery + + result = eval(<<~RUBY) + case {} + in Array true end RUBY + end - eval(<<~RUBY).should == true - case 0 - in ^(0+0) + 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 + + it "supports pinning instance variables" do + eval(<<~RUBY).should == true + @a = /a/ + case 'abc' + in ^@a + true + end + RUBY + end + + it "supports pinning class variables" do + result = nil + Module.new do + result = module_eval(<<~RUBY) + @@a = 0..10 + + case 2 + in ^@@a true end RUBY end + + result.should == true + end + + it "supports pinning global variables" do + eval(<<~RUBY).should == true + $a = /a/ + case 'abc' + in ^$a + true + end + RUBY + end + + it "supports pinning expressions" do + eval(<<~RUBY).should == true + case 'abc' + in ^(/a/) + true + end + RUBY + + 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 + end + RUBY + + eval(<<~RUBY).should == true + case 0 + in ^(0+0) + true + end + RUBY end end end diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index d04dfcf251..b5eda7f789 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -654,10 +654,8 @@ describe "Predefined global $," do -> { $, = Object.new }.should raise_error(TypeError) end - ruby_version_is "2.7" do - it "warns if assigned non-nil" do - -> { $, = "_" }.should complain(/warning: `\$,' is deprecated/) - end + it "warns if assigned non-nil" do + -> { $, = "_" }.should complain(/warning: `\$,' is deprecated/) end end @@ -693,10 +691,8 @@ describe "Predefined global $;" do $; = nil end - ruby_version_is "2.7" do - it "warns if assigned non-nil" do - -> { $; = "_" }.should complain(/warning: `\$;' is deprecated/) - end + it "warns if assigned non-nil" do + -> { $; = "_" }.should complain(/warning: `\$;' is deprecated/) end end @@ -1316,33 +1312,31 @@ describe "The predefined global constant" do end end -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 +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" + 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" - extension, path = $LOAD_PATH.resolve_feature_path('etc') - extension.should == :so - path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}") - end + extension, path = $LOAD_PATH.resolve_feature_path('etc') + extension.should == :so + path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}") + end - 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 + 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) 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 - 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 end end end diff --git a/spec/ruby/language/proc_spec.rb b/spec/ruby/language/proc_spec.rb index ef4a43bed6..8360967ec8 100644 --- a/spec/ruby/language/proc_spec.rb +++ b/spec/ruby/language/proc_spec.rb @@ -223,13 +223,7 @@ describe "A Proc" do @p = proc { |*a, **kw| [a, kw] } end - 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 + ruby_version_is ""..."3.0" do it 'autosplats keyword arguments and warns' do -> { @p.call([1, {a: 1}]).should == [[1], {a: 1}] diff --git a/spec/ruby/language/range_spec.rb b/spec/ruby/language/range_spec.rb index 79500c6b33..55dc65882a 100644 --- a/spec/ruby/language/range_spec.rb +++ b/spec/ruby/language/range_spec.rb @@ -15,10 +15,8 @@ describe "Literal Ranges" do (1...).should == Range.new(1, nil, true) 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 + it "creates beginless ranges" do + (..1).should == Range.new(nil, 1) + (...1).should == Range.new(nil, 1, true) end end diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index fcd9ae0de9..b91b52fa0f 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -504,14 +504,12 @@ describe "The rescue keyword" do }.should raise_error(Exception) end - ruby_version_is "2.7" do - it "rescues with multiple assignment" 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 - end + a.should == 1 + b.should == 2 end end end diff --git a/spec/ruby/language/return_spec.rb b/spec/ruby/language/return_spec.rb index d8506834c8..94c15b697e 100644 --- a/spec/ruby/language/return_spec.rb +++ b/spec/ruby/language/return_spec.rb @@ -422,18 +422,16 @@ describe "The return keyword" do end describe "within a block within a class" do - 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 + 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 @@ -464,25 +462,13 @@ describe "The return keyword" do end describe "return with argument" do - 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 + 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/ - end + err.should =~ /warning: argument of top-level return is ignored/ end end end diff --git a/spec/ruby/language/safe_spec.rb b/spec/ruby/language/safe_spec.rb index 89830a2069..ee5c1b3ccc 100644 --- a/spec/ruby/language/safe_spec.rb +++ b/spec/ruby/language/safe_spec.rb @@ -1,99 +1,7 @@ require_relative '../spec_helper' describe "The $SAFE variable" do - 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 + ruby_version_is ""..."3.0" do it "warn when access" do -> { $SAFE diff --git a/spec/ruby/language/send_spec.rb b/spec/ruby/language/send_spec.rb index e57e2c65dc..5999079d58 100644 --- a/spec/ruby/language/send_spec.rb +++ b/spec/ruby/language/send_spec.rb @@ -258,20 +258,10 @@ describe "Invoking a private setter method" do end describe "Invoking a private getter method" do - 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 + 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 diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb index ce4941569e..02e3488a1f 100644 --- a/spec/ruby/language/string_spec.rb +++ b/spec/ruby/language/string_spec.rb @@ -56,28 +56,6 @@ 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" diff --git a/spec/ruby/language/yield_spec.rb b/spec/ruby/language/yield_spec.rb index 3db6d353a9..85bd5af25b 100644 --- a/spec/ruby/language/yield_spec.rb +++ b/spec/ruby/language/yield_spec.rb @@ -186,7 +186,7 @@ describe "The yield call" do end describe "Using yield in a singleton class literal" do - ruby_version_is "2.7"..."3.0" do + ruby_version_is ""..."3.0" do it 'emits a deprecation warning' do code = <<~RUBY def m |