diff options
author | Andrew Konchin <[email protected]> | 2024-06-07 19:01:53 +0300 |
---|---|---|
committer | Benoit Daloze <[email protected]> | 2024-06-10 16:00:37 +0200 |
commit | 3ebab4b64d56e9e13a2b954a7a25514fd43f0895 (patch) | |
tree | a4f9389c7b69165002e5d67e75e074ded1e30c3a /spec/ruby/language | |
parent | e8bd745c17b809ba1a64e33fde91edd5babe4500 (diff) |
Update to ruby/spec@517f06f
Diffstat (limited to 'spec/ruby/language')
-rw-r--r-- | spec/ruby/language/case_spec.rb | 2 | ||||
-rw-r--r-- | spec/ruby/language/pattern_matching/3.1.rb | 75 | ||||
-rw-r--r-- | spec/ruby/language/pattern_matching_spec.rb | 1468 |
3 files changed, 660 insertions, 885 deletions
diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb index d289eca037..464d06e46a 100644 --- a/spec/ruby/language/case_spec.rb +++ b/spec/ruby/language/case_spec.rb @@ -427,7 +427,7 @@ describe "The 'case'-construct" do :bar end RUBY - }.should complain(/warning: duplicated .when' clause with line \d+ is ignored/, verbose: true) + }.should complain(/warning: (duplicated .when' clause with line \d+ is ignored|'when' clause on line \d+ duplicates 'when' clause on line \d+ and is ignored)/, verbose: true) end end diff --git a/spec/ruby/language/pattern_matching/3.1.rb b/spec/ruby/language/pattern_matching/3.1.rb new file mode 100644 index 0000000000..7a09084e41 --- /dev/null +++ b/spec/ruby/language/pattern_matching/3.1.rb @@ -0,0 +1,75 @@ +describe "Pattern matching" do + before :each do + ScratchPad.record [] + end + + describe "Ruby 3.1 improvements" do + ruby_version_is "3.1" do + it "can omit parentheses in one line pattern matching" do + [1, 2] => a, b + [a, b].should == [1, 2] + + {a: 1} => a: + a.should == 1 + end + + it "supports pinning instance variables" do + @a = /a/ + case 'abc' + in ^@a + true + end.should == true + 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 + $a = /a/ + case 'abc' + in ^$a + true + end.should == true + end + + it "supports pinning expressions" do + case 'abc' + in ^(/a/) + true + end.should == true + + case 0 + in ^(0 + 0) + true + end.should == true + end + + it "supports pinning expressions in array pattern" do + case [3] + in [^(1 + 2)] + true + end.should == true + end + + it "supports pinning expressions in hash pattern" do + case {name: '2.6', released_at: Time.new(2018, 12, 25)} + in {released_at: ^(Time.new(2010)..Time.new(2020))} + true + end.should == true + end + end + end +end diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb index a8ec078cd0..8c5df06a17 100644 --- a/spec/ruby/language/pattern_matching_spec.rb +++ b/spec/ruby/language/pattern_matching_spec.rb @@ -1,9 +1,6 @@ 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. - before :each do ScratchPad.record [] end @@ -11,145 +8,119 @@ describe "Pattern matching" do 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 + [0, 1] => [a, b] + [a, b].should == [0, 1] end end it "deconstructs value and properly scopes variables" do suppress_warning do - eval(<<-RUBY).should == [0, nil] - a = nil - eval(<<-PATTERN) - [0, 1] => [a, b] - PATTERN - [a, defined?(b)] - RUBY + a = nil + 1.times { + [0, 1] => [a, b] + } + [a, defined?(b)].should == [0, nil] 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 + case [0, 1, 2, 3] + in [*pre, 2, 3] + pre + else + false + end.should == [0, 1] 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 + case [0, 1, 2, 3] + in [0, 1, *post] + post + else + false + end.should == [2, 3] 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 + case [0, 1, 2, 3, 4] + in [*pre, 2, *post] + [pre, post] + else + false + end.should == [[0, 1], [3, 4]] 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 + case [0, 1, 2, 3, 4] + in [*everything] + everything + else + false + end.should == [0, 1, 2, 3, 4] end it "will match an empty Array-like structure" do - eval(<<~RUBY).should == [] - case [] - in [*everything] - everything - else - false - end - RUBY + case [] + in [*everything] + everything + else + false + end.should == [] 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 + case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]] + in [*pre, [*, 9, a], *post] + [pre, post, a] + else + false + end.should == [[0, [2, 4, 6]], [[4, 16, 64]], 27] 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 + case [0, [2, 4, 6], [3, 9, 27], [4, 16, 64]] + in [_, _, [*, 9, *], *post] + post + else + false + end.should == [[4, 16, 64]] 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 + case {a: [3, 9, 27]} + in {a: [*, 9, *post]} + post + else + false + end.should == [27] 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 + case [0, {a: 42, b: [0, 1]}, {a: 42, b: [1, 2]}] + in [*, {a:, b: [1, c]}, *] + [a, c] + else + false + end.should == [42, 2] 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 + case [0, 1] + in [0] + :foo + in [0, 1] + :bar + end.should == :bar 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 + case [0, 1] + in [0] then :foo + in [0, 1] then :bar + end.should == :bar end describe "warning" do @@ -191,12 +162,10 @@ describe "Pattern matching" do end it "binds variables" do - eval(<<~RUBY).should == 1 - case [0, 1] - in [0, a] - a - end - RUBY + case [0, 1] + in [0, a] + a + end.should == 1 end it "cannot mix in and when operators" do @@ -220,47 +189,45 @@ describe "Pattern matching" do 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 + case [0, 1] + in [0] + :foo + in [0, 1] + :bar + in [0, 1] + :baz + end.should == :bar end it "executes else clause if no pattern matches" do - eval(<<~RUBY).should == false - case [0, 1] - in [0] - true - else - false - end - RUBY + case [0, 1] + in [0] + true + else + false + end.should == false end it "raises NoMatchingPatternError if no pattern matches and no else clause" do -> { - eval <<~RUBY - case [0, 1] - in [0] - end - RUBY + case [0, 1] + in [0] + end }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) + + -> { + case {a: 0, b: 1} + in a: 1, b: 1 + end + }.should raise_error(NoMatchingPatternError, /\{:a=>0, :b=>1\}/) 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 + case (evals += 1; [0, 1]) + in [0] + end }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) evals.should == 1 end @@ -273,230 +240,185 @@ describe "Pattern matching" do true end RUBY - }.should raise_error(SyntaxError, /unexpected|expected a delimiter after the predicates of a `when` clause/) + }.should raise_error(SyntaxError, /unexpected|expected a delimiter after the patterns of an `in` clause/) 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 + case (ScratchPad << :foo; 1) + in 0 + false + in 1 + true + end.should == true 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 + case 0 + in 0 if false + true + else + false + end.should == false - eval(<<~RUBY).should == true - case 0 - in 0 if true - true - else - false - end - RUBY + case 0 + in 0 if true + true + else + false + end.should == true end it "supports unless guard" do - eval(<<~RUBY).should == false - case 0 - in 0 unless true - true - else - false - end - RUBY + case 0 + in 0 unless true + true + else + false + end.should == false - eval(<<~RUBY).should == true - case 0 - in 0 unless false - true - else - false - end - RUBY + case 0 + in 0 unless false + true + else + false + end.should == true 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 + case [0, 1] + in [a, 1] if a >= 0 + true + end.should == true 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 + case 0 + in 1 if (ScratchPad << :foo) || true + else + end ScratchPad.recorded.should == [] 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 + case 0 + in 0 if false + :foo + in 0 if true + :bar + end.should == :bar end 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 + case 0 + in 0 if false + true + else + false + end.should == false end 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 + case [0, 1] + in [0, 1] if false + end }.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 + case 0 + in 0 + true + end.should == true - eval(<<~RUBY).should == true - case 0 - in (-1..1) - true - end - RUBY + case 0 + in ( + -1..1) + true + end.should == true - eval(<<~RUBY).should == true - case 0 - in Integer - true - end - RUBY + case 0 + in Integer + true + end.should == true - eval(<<~RUBY).should == true - case "0" - in /0/ - true - end - RUBY + case "0" + in /0/ + true + end.should == true - eval(<<~RUBY).should == true - case "0" - in ->(s) { s == "0" } - true - end - RUBY + case "0" + in -> s { s == "0" } + true + end.should == true end it "allows string literal with interpolation" do x = "x" - eval(<<~RUBY).should == true - case "x" - in "#{x + ""}" - true - end - RUBY + case "x" + in "#{x + ""}" + true + end.should == true 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 + case 0 + in a + a + end.should == 0 end it "makes bounded variable visible outside a case statement scope" do - eval(<<~RUBY).should == 0 - case 0 - in a - end + case 0 + in a + end - a - RUBY + a.should == 0 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 + case 0 + in a + in b + in c + end - [a, b, c] - RUBY + [a, b, c].should == [0, nil, nil] end it "allow using _ name to drop values" do - eval(<<~RUBY).should == 0 - case [0, 1] - in [a, _] - a - end - RUBY + case [0, 1] + in [a, _] + a + end.should == 0 end it "supports using _ in a pattern several times" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in [0, _, _] - true - end - RUBY + case [0, 1, 2] + in [0, _, _] + true + end.should == true 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 + case [0, 1, 2] + in [0, _x, _x] + true + end.should == true - eval(<<~RUBY).should == true - case {a: 0, b: 1, c: 2} - in {a: 0, b: _x, c: _x} - true - end - RUBY + case {a: 0, b: 1, c: 2} + in {a: 0, b: _x, c: _x} + true + end.should == true end it "does not support using variable name (except _) several times" do @@ -512,30 +434,24 @@ describe "Pattern matching" do it "supports existing variables in a pattern specified with ^ operator" do a = 0 - eval(<<~RUBY).should == true - case 0 - in ^a - true - end - RUBY + case 0 + in ^a + true + end.should == true end it "allows applying ^ operator to bound variables" do - eval(<<~RUBY).should == 1 - case [1, 1] - in [n, ^n] - n - end - RUBY + case [1, 1] + in [n, ^n] + n + end.should == 1 - eval(<<~RUBY).should == false - case [1, 2] - in [n, ^n] - true - else - false - end - RUBY + case [1, 2] + in [n, ^n] + true + else + false + end.should == false end it "requires bound variable to be specified in a pattern before ^ operator when it relies on a bound variable" do @@ -554,12 +470,10 @@ describe "Pattern matching" do 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 + case 0 + in 0 | 1 | 2 + true + end.should == true end it "does not support variable binding" do @@ -573,120 +487,99 @@ describe "Pattern matching" do 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 + case [0, 1] + in [1, _] + false + in [0, 0] | [0, _a] + true + end.should == true 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 + case [[1], ["2"]] + in [[0] | nil, _] + false + in [[1], [1]] + false + in [[1], [2 | "2"]] + true + end.should == true - eval(<<~RUBY).should == true - case [1, 2] - in [0, _] | {a: 0} - false - in {a: 1, b: 2} | [1, 2] - true - end - RUBY + case [1, 2] + in [0, _] | {a: 0} + false + in {a: 1, b: 2} | [1, 2] + true + end.should == true end 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 + case 0 + in Integer => n + n + end.should == 0 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 + case [1, [2, 3]] + in [1, Array => ary] + ary + end.should == [2, 3] end 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 + case [0, 1, 2] + in Array(0, 1, 2) + true + end.should == true end it "supports form Constant[pat, pat, ...]" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in Array[0, 1, 2] - true - end - RUBY + case [0, 1, 2] + in Array[0, 1, 2] + true + end.should == true end it "supports form [pat, pat, ...]" do - eval(<<~RUBY).should == true - case [0, 1, 2] - in [0, 1, 2] - true - end - RUBY + case [0, 1, 2] + in [0, 1, 2] + true + end.should == true end it "supports form pat, pat, ..." do - eval(<<~RUBY).should == true - case [0, 1, 2] - in 0, 1, 2 - true - end - RUBY + case [0, 1, 2] + in 0, 1, 2 + true + end.should == true - eval(<<~RUBY).should == 1 - case [0, 1, 2] - in 0, a, 2 - a - end - RUBY + case [0, 1, 2] + in 0, a, 2 + a + end.should == 1 - eval(<<~RUBY).should == [1, 2] - case [0, 1, 2] - in 0, *rest - rest - end - RUBY + case [0, 1, 2] + in 0, *rest + rest + end.should == [1, 2] 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 - eval(<<~RUBY).should == true - case obj - in [Integer, Integer] - true - end - RUBY + def obj.deconstruct + [0, 1] + end + + case obj + in [Integer, Integer] + true + end.should == true end it "calls #deconstruct once for multiple patterns, caching the result" do @@ -697,314 +590,267 @@ describe "Pattern matching" do [0, 1] end - eval(<<~RUBY).should == true - case obj - in [1, 2] - false - in [0, 1] - true - end - RUBY + case obj + in [1, 2] + false + in [0, 1] + true + end.should == true ScratchPad.recorded.should == [:deconstruct] end it "calls #deconstruct even on objects that are already an array" do obj = [1, 2] + def obj.deconstruct ScratchPad << :deconstruct [3, 4] end - eval(<<~RUBY).should == true - case obj - in [3, 4] - true - else - false - end - RUBY + case obj + in [3, 4] + true + else + false + end.should == true 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 - end - RUBY + case [0, 1, 2] + in String[0, 1, 2] + true + else + false + end.should == false end it "checks Constant === object before calling #deconstruct" do c1 = Class.new obj = c1.new obj.should_not_receive(:deconstruct) - eval(<<~RUBY).should == false - case obj - in String[1] - true - else - false - end - RUBY + + case obj + in String[1] + true + else + false + end.should == false 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 + case obj + in Object[] + true + else + false + end.should == false end it "raises TypeError if #deconstruct method does not return array" do obj = Object.new - def obj.deconstruct; "" end + + def obj.deconstruct + "" + end -> { - eval <<~RUBY - case obj - in Object[] - else - end - RUBY + case obj + in Object[] + else + end }.should raise_error(TypeError, /deconstruct must return Array/) end it "accepts a subclass of Array from #deconstruct" do obj = Object.new + def obj.deconstruct Class.new(Array).new([0, 1]) end - eval(<<~RUBY).should == true - case obj - in [1, 2] - false - in [0, 1] - true - end - RUBY + case obj + in [1, 2] + false + in [0, 1] + true + end.should == true 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 - eval(<<~RUBY).should == false - case obj - in Object[0] - true - else - false - end - RUBY + def obj.deconstruct + [1] + end + + case obj + in Object[0] + true + else + false + end.should == false end it "binds variables" do - eval(<<~RUBY).should == [0, 1, 2] - case [0, 1, 2] - in [a, b, c] - [a, b, c] - end - RUBY + case [0, 1, 2] + in [a, b, c] + [a, b, c] + end.should == [0, 1, 2] end it "supports splat operator *rest" do - eval(<<~RUBY).should == [1, 2] - case [0, 1, 2] - in [0, *rest] - rest - end - RUBY + case [0, 1, 2] + in [0, *rest] + rest + end.should == [1, 2] 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 + case [0, 1, 2, 3] + in [1, 2] + true + else + false + end.should == false 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 + case [0, 1, 2, 3] + in [0, 1, ] + true + end.should == true - eval(<<~RUBY).should == true - case [0, 1, 2, 3] - in 0, 1,; - true - end - RUBY + case [0, 1, 2, 3] + in 0, 1,; + true + end.should == true end it "matches [] with []" do - eval(<<~RUBY).should == true - case [] - in [] - true - end - RUBY + case [] + in [] + true + end.should == true end it "matches anything with *" do - eval(<<~RUBY).should == true - case [0, 1] - in *; - true - end - RUBY + case [0, 1] + in *; + true + end.should == true 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 + case [[1], ["2"]] + in [[0] | nil, _] + false + in [[1], [1]] + false + in [[1], [2 | "2"]] + true + end.should == true - eval(<<~RUBY).should == true - case [1, 2] - in [0, _] | {a: 0} - false - in {a: 1, b: 2} | [1, 2] - true - end - RUBY + case [1, 2] + in [0, _] | {a: 0} + false + in {a: 1, b: 2} | [1, 2] + true + end.should == true end 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 + case {a: 0, b: 1} + in Hash(a: 0, b: 1) + true + end.should == true 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 + case {a: 0, b: 1} + in Hash[a: 0, b: 1] + true + end.should == true 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 + case {a: 0, b: 1} + in {a: 0, b: 1} + true + end.should == true 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 + case {a: 0, b: 1} + in a: 0, b: 1 + true + end.should == true - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in a: a, b: b - [a, b] - end - RUBY + case {a: 0, b: 1} + in a: a, b: b + [a, b] + end.should == [0, 1] - eval(<<~RUBY).should == { b: 1, c: 2 } - case {a: 0, b: 1, c: 2} - in a: 0, **rest - rest - end - RUBY + case {a: 0, b: 1, c: 2} + in a: 0, **rest + rest + end.should == {b: 1, c: 2} 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 + case {a: 0, b: 1} + in Hash(a:, b:) + [a, b] + end.should == [0, 1] a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in Hash[a:, b:] - [a, b] - end - RUBY + + case {a: 0, b: 1} + in Hash[a:, b:] + [a, b] + end.should == [0, 1] a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in {a:, b:} - [a, b] - end - RUBY + + case {a: 0, b: 1} + in {a:, b:} + [a, b] + end.should == [0, 1] a = nil - eval(<<~RUBY).should == [0, {b: 1, c: 2}] - case {a: 0, b: 1, c: 2} - in {a:, **rest} - [a, rest] - end - RUBY + + case {a: 0, b: 1, c: 2} + in {a:, **rest} + [a, rest] + end.should == [0, {b: 1, c: 2}] a = b = nil - eval(<<~RUBY).should == [0, 1] - case {a: 0, b: 1} - in a:, b: - [a, b] - end - RUBY + + case {a: 0, b: 1} + in a:, b: + [a, b] + end.should == [0, 1] 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 + case {a: 0, b: 1} + in Hash(a:, b: x) + [a, x] + end.should == [0, 1] end it "supports 'string': key literal" do - eval(<<~RUBY).should == true - case {a: 0} - in {"a": 0} - true - end - RUBY + case {a: 0} + in {"a": 0} + true + end.should == true end it "does not support non-symbol keys" do @@ -1018,8 +864,6 @@ describe "Pattern matching" do end it "does not support string interpolation in keys" do - x = "a" - -> { eval <<~'RUBY' case {a: 1} @@ -1041,14 +885,15 @@ describe "Pattern matching" do 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 {a: 1} - true - end - RUBY + def obj.deconstruct_keys(*) + {a: 1} + end + + case obj + in {a: 1} + true + end.should == true end it "calls #deconstruct_keys per pattern" do @@ -1059,96 +904,92 @@ describe "Pattern matching" do {a: 1} end - eval(<<~RUBY).should == true - case obj - in {b: 1} - false - in {a: 1} - true - end - RUBY + case obj + in {b: 1} + false + in {a: 1} + true + end.should == true ScratchPad.recorded.should == [:deconstruct_keys, :deconstruct_keys] 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 + case {a: 1} + in String[a: 1] + true + else + false + end.should == false end it "checks Constant === object before calling #deconstruct_keys" do c1 = Class.new obj = c1.new obj.should_not_receive(:deconstruct_keys) - eval(<<~RUBY).should == false - case obj - in String(a: 1) - true - else - false - end - RUBY + + case obj + in String(a: 1) + true + else + false + end.should == false end it "does not match object without #deconstruct_keys method" do obj = Object.new obj.should_receive(:respond_to?).with(:deconstruct_keys) - eval(<<~RUBY).should == false - case obj - in Object[a: 1] - true - else - false - end - RUBY + case obj + in Object[a: 1] + true + else + false + end.should == false end it "does not match object if #deconstruct_keys method does not return Hash" do obj = Object.new - def obj.deconstruct_keys(*); "" end + + def obj.deconstruct_keys(*) + "" + end -> { - eval <<~RUBY - case obj - in Object[a: 1] - end - RUBY + case obj + in Object[a: 1] + end }.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 + def obj.deconstruct_keys(*) + {"a" => 1} + end + + case obj + in Object[a: 1] + true + else + false + end.should == false 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 - eval(<<~RUBY).should == false - case obj - in Object[a: 2] - true - else - false - end - RUBY + def obj.deconstruct_keys(*) + {a: 1} + end + + case obj + in Object[a: 2] + true + else + false + end.should == false end it "passes keys specified in pattern as arguments to #deconstruct_keys method" do @@ -1159,11 +1000,9 @@ describe "Pattern matching" do {a: 1, b: 2, c: 3} end - eval <<~RUBY - case obj - in Object[a: 1, b: 2, c: 3] - end - RUBY + case obj + in Object[a: 1, b: 2, c: 3] + end ScratchPad.recorded.sort.should == [[[:a, :b, :c]]] end @@ -1176,11 +1015,9 @@ describe "Pattern matching" do {a: 1, b: 2, c: 3} end - eval <<~RUBY - case obj - in Object[a: 1, b: 2, **] - end - RUBY + case obj + in Object[a: 1, b: 2, **] + end ScratchPad.recorded.sort.should == [[[:a, :b]]] end @@ -1193,131 +1030,105 @@ describe "Pattern matching" do {a: 1, b: 2} end - eval <<~RUBY - case obj - in Object[a: 1, **rest] - end - RUBY + case obj + in Object[a: 1, **rest] + end 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] - end - RUBY + case {a: 0, b: 1, c: 2} + in {a: x, b: y, c: z} + [x, y, z] + end.should == [0, 1, 2] 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 + case {a: 0, b: 1, c: 2} + in {a: 0, **rest} + rest + end.should == {b: 1, c: 2} 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 + case {a: 1, b: 2} + in {a: 1, b: 2, **nil} + true + end.should == true - eval(<<~RUBY).should == false - case {a: 1, b: 2} - in {a: 1, **nil} - true - else - false - end - RUBY + case {a: 1, b: 2} + in {a: 1, **nil} + true + else + false + end.should == false end it "can match partially" do - eval(<<~RUBY).should == true - case {a: 1, b: 2} - in {a: 1} - true - end - RUBY + case {a: 1, b: 2} + in {a: 1} + true + end.should == true end it "matches {} with {}" do - eval(<<~RUBY).should == true - case {} - in {} - true - end - RUBY + case {} + in {} + true + end.should == true end it "in {} only matches empty hashes" do - eval(<<~RUBY).should == false - case {a: 1} - in {} - true - else - false - end - RUBY + case {a: 1} + in {} + true + else + false + end.should == false end it "in {**nil} only matches empty hashes" do - eval(<<~RUBY).should == true - case {} - in {**nil} - true - else - false - end - RUBY + case {} + in {**nil} + true + else + false + end.should == true - eval(<<~RUBY).should == false - case {a: 1} - in {**nil} - true - else - false - end - RUBY + case {a: 1} + in {**nil} + true + else + false + end.should == false end it "matches anything with **" do - eval(<<~RUBY).should == true - case {a: 1} - in **; - true - end - RUBY + case {a: 1} + in **; + true + end.should == true 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 + 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.should == 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 + 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.should == true end end @@ -1335,12 +1146,11 @@ describe "Pattern matching" do Module.new do using refinery - result = eval(<<~RUBY) + result = case [] in [0] true end - RUBY end result.should == true @@ -1359,12 +1169,11 @@ describe "Pattern matching" do Module.new do using refinery - result = eval(<<~RUBY) + result = case {} in a: 0 true end - RUBY end result.should == true @@ -1383,127 +1192,18 @@ describe "Pattern matching" do Module.new do using refinery - result = eval(<<~RUBY) + result = case {} in Array true end - RUBY end result.should == true end end +end - describe "Ruby 3.1 improvements" do - 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 0 - in ^(0+0) - true - end - RUBY - end - - it "supports pinning expressions in array pattern" do - eval(<<~RUBY).should == true - case [3] - in [^(1+2)] - true - end - RUBY - end - - it "supports pinning expressions in hash pattern" do - 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 - end - end - end - - describe "value in pattern" do - it "returns true if the pattern matches" do - eval("1 in 1").should == true - - eval("1 in Integer").should == true - - e = nil - eval("[1, 2] in [1, e]").should == true - e.should == 2 - - k = nil - eval("{k: 1} in {k:}").should == true - k.should == 1 - end - - it "returns false if the pattern does not match" do - eval("1 in 2").should == false - - eval("1 in Float").should == false - - eval("[1, 2] in [2, e]").should == false - - eval("{k: 1} in {k: 2}").should == false - end - end +ruby_version_is "3.1" do + require_relative 'pattern_matching/3.1' end |