diff options
author | Benoit Daloze <eregontp@gmail.com> | 2020-12-27 17:35:32 +0100 |
---|---|---|
committer | Benoit Daloze <eregontp@gmail.com> | 2020-12-27 17:35:32 +0100 |
commit | 727c97da1977544c91b9b3677811da3a44af7d53 (patch) | |
tree | 4f027117edad10789db57ff4b83242753a89e39d /spec/ruby/language | |
parent | 267bed0cd91711e2a8c79219e97431ba22137b01 (diff) | |
download | ruby-727c97da1977544c91b9b3677811da3a44af7d53.tar.gz |
Update to ruby/spec@4ce9f41
Diffstat (limited to 'spec/ruby/language')
-rw-r--r-- | spec/ruby/language/defined_spec.rb | 4 | ||||
-rw-r--r-- | spec/ruby/language/numbered_parameters_spec.rb | 2 | ||||
-rw-r--r-- | spec/ruby/language/pattern_matching_spec.rb | 106 | ||||
-rw-r--r-- | spec/ruby/language/regexp/back-references_spec.rb | 82 | ||||
-rw-r--r-- | spec/ruby/language/regexp/escapes_spec.rb | 16 | ||||
-rw-r--r-- | spec/ruby/language/regexp/grouping_spec.rb | 5 | ||||
-rw-r--r-- | spec/ruby/language/regexp/repetition_spec.rb | 83 | ||||
-rw-r--r-- | spec/ruby/language/regexp/subexpression_call_spec.rb | 50 | ||||
-rw-r--r-- | spec/ruby/language/singleton_class_spec.rb | 2 |
9 files changed, 344 insertions, 6 deletions
diff --git a/spec/ruby/language/defined_spec.rb b/spec/ruby/language/defined_spec.rb index 4fcf869f91..f2da8a9293 100644 --- a/spec/ruby/language/defined_spec.rb +++ b/spec/ruby/language/defined_spec.rb @@ -435,11 +435,11 @@ describe "The defined? keyword for an expression" do end end - it "returns 'expression' when passed an Integer literal" do + it "returns 'expression' when passed a Fixnum literal" do defined?(42).should == "expression" end - it "returns 'expression' when passed an Integer literal" do + it "returns 'expression' when passed a Bignum literal" do defined?(0xdead_beef_deed_feed).should == "expression" end diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb index b05c373a68..cd1ddf4555 100644 --- a/spec/ruby/language/numbered_parameters_spec.rb +++ b/spec/ruby/language/numbered_parameters_spec.rb @@ -29,7 +29,7 @@ ruby_version_is "2.7" do 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.+ outer block here/m) + }.should raise_error(SyntaxError, /numbered parameter is already used in/m) end ruby_version_is '2.7'...'3.0' do diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb index edc3826448..dae2828bf6 100644 --- a/spec/ruby/language/pattern_matching_spec.rb +++ b/spec/ruby/language/pattern_matching_spec.rb @@ -5,7 +5,7 @@ ruby_version_is "2.7" do # TODO: Remove excessive eval calls when support of previous version # Ruby 2.6 will be dropped - before do + before :each do ScratchPad.record [] end @@ -41,7 +41,7 @@ ruby_version_is "2.7" do end describe "warning" do - before do + before :each do ruby_version_is ""..."3.0" do @src = 'case [0, 1]; in [a, b]; end' end @@ -49,6 +49,12 @@ ruby_version_is "2.7" do ruby_version_is "3.0" do @src = '[0, 1] => [a, b]' end + + @experimental, Warning[:experimental] = Warning[:experimental], true + end + + after :each do + Warning[:experimental] = @experimental end it "warns about pattern matching is experimental feature" do @@ -130,6 +136,19 @@ ruby_version_is "2.7" do }.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 @@ -508,6 +527,47 @@ ruby_version_is "2.7" do RUBY end + ruby_version_is "3.0" do + it "calls #deconstruct once for multiple patterns, caching the result" do + obj = Object.new + + def obj.deconstruct + ScratchPad << :deconstruct + [0, 1] + end + + eval(<<~RUBY).should == true + case obj + in [1, 2] + false + in [0, 1] + true + end + RUBY + + ScratchPad.recorded.should == [:deconstruct] + end + 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 + + ScratchPad.recorded.should == [:deconstruct] + end + it "does not match object if Constant === object returns false" do eval(<<~RUBY).should == false case [0, 1, 2] @@ -521,6 +581,7 @@ ruby_version_is "2.7" do 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 @@ -546,6 +607,26 @@ ruby_version_is "2.7" do }.should raise_error(TypeError, /deconstruct must return Array/) end + it "accepts a subclass of Array from #deconstruct" do + obj = Object.new + def obj.deconstruct + subarray = Class.new(Array).new(2) + def subarray.[](n) + n + end + subarray + end + + eval(<<~RUBY).should == true + case obj + in [1, 2] + false + in [0, 1] + true + end + RUBY + end + it "does not match object if elements of array returned by #deconstruct method does not match elements in pattern" do obj = Object.new def obj.deconstruct; [1] end @@ -778,6 +859,26 @@ ruby_version_is "2.7" do RUBY end + it "calls #deconstruct_keys per pattern" do + obj = Object.new + + def obj.deconstruct_keys(*) + ScratchPad << :deconstruct_keys + {a: 1} + end + + eval(<<~RUBY).should == true + case obj + in {b: 1} + false + in {a: 1} + true + end + RUBY + + 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} @@ -791,6 +892,7 @@ ruby_version_is "2.7" do 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 diff --git a/spec/ruby/language/regexp/back-references_spec.rb b/spec/ruby/language/regexp/back-references_spec.rb index 81015ac21e..e8df8725c5 100644 --- a/spec/ruby/language/regexp/back-references_spec.rb +++ b/spec/ruby/language/regexp/back-references_spec.rb @@ -47,7 +47,89 @@ describe "Regexps with back-references" do /(a\1?){2}/.match("aaaa").to_a.should == ["aa", "a"] end + it "does not reset enclosed capture groups" do + /((a)|(b))+/.match("ab").captures.should == [ "b", "a", "b" ] + end + it "can match an optional quote, followed by content, followed by a matching quote, as the whole string" do /^("|)(.*)\1$/.match('x').to_a.should == ["x", "", "x"] end + + it "allows forward references" do + /(?:(\2)|(.))+/.match("aa").to_a.should == [ "aa", "a", "a" ] + end + + it "disallows forward references >= 10" do + (/\10()()()()()()()()()()/ =~ "\x08").should == 0 + end + + it "ignores backreferences > 1000" do + /\99999/.match("99999")[0].should == "99999" + end + + it "0 is not a valid backreference" do + -> { Regexp.new("\\k<0>") }.should raise_error(RegexpError) + end + + it "allows numeric conditional backreferences" do + /(a)(?(1)a|b)/.match("aa").to_a.should == [ "aa", "a" ] + /(a)(?(<1>)a|b)/.match("aa").to_a.should == [ "aa", "a" ] + /(a)(?('1')a|b)/.match("aa").to_a.should == [ "aa", "a" ] + end + + it "allows either <> or '' in named conditional backreferences" do + -> { Regexp.new("(?<a>a)(?(a)a|b)") }.should raise_error(RegexpError) + /(?<a>a)(?(<a>)a|b)/.match("aa").to_a.should == [ "aa", "a" ] + /(?<a>a)(?('a')a|b)/.match("aa").to_a.should == [ "aa", "a" ] + end + + it "allows negative numeric backreferences" do + /(a)\k<-1>/.match("aa").to_a.should == [ "aa", "a" ] + /(a)\g<-1>/.match("aa").to_a.should == [ "aa", "a" ] + /(a)(?(<-1>)a|b)/.match("aa").to_a.should == [ "aa", "a" ] + /(a)(?('-1')a|b)/.match("aa").to_a.should == [ "aa", "a" ] + end + + it "delimited numeric backreferences can start with 0" do + /(a)\k<01>/.match("aa").to_a.should == [ "aa", "a" ] + /(a)\g<01>/.match("aa").to_a.should == [ "aa", "a" ] + /(a)(?(01)a|b)/.match("aa").to_a.should == [ "aa", "a" ] + /(a)(?(<01>)a|b)/.match("aa").to_a.should == [ "aa", "a" ] + /(a)(?('01')a|b)/.match("aa").to_a.should == [ "aa", "a" ] + end + + it "regular numeric backreferences cannot start with 0" do + /(a)\01/.match("aa").should == nil + /(a)\01/.match("a\x01").to_a.should == [ "a\x01", "a" ] + end + + it "named capture groups invalidate numeric backreferences" do + -> { Regexp.new("(?<a>a)\\1") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a>a)\\k<1>") }.should raise_error(RegexpError) + -> { Regexp.new("(a)(?<a>a)\\1") }.should raise_error(RegexpError) + -> { Regexp.new("(a)(?<a>a)\\k<1>") }.should raise_error(RegexpError) + end + + it "treats + or - as the beginning of a level specifier in \\k<> backreferences and (?(...)...|...) conditional backreferences" do + -> { Regexp.new("(?<a+>a)\\k<a+>") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a+b>a)\\k<a+b>") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a+1>a)\\k<a+1>") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a->a)\\k<a->") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a-b>a)\\k<a-b>") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a-1>a)\\k<a-1>") }.should raise_error(RegexpError) + + -> { Regexp.new("(?<a+>a)(?(<a+>)a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a+b>a)(?(<a+b>)a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a+1>a)(?(<a+1>)a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a->a)(?(<a->)a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a-b>a)(?(<a-b>)a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a-1>a)(?(<a-1>)a|b)") }.should raise_error(RegexpError) + + -> { Regexp.new("(?<a+>a)(?('a+')a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a+b>a)(?('a+b')a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a+1>a)(?('a+1')a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a->a)(?('a-')a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a-b>a)(?('a-b')a|b)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<a-1>a)(?('a-1')a|b)") }.should raise_error(RegexpError) + end end diff --git a/spec/ruby/language/regexp/escapes_spec.rb b/spec/ruby/language/regexp/escapes_spec.rb index 14e1424d47..2e5fe5ad2e 100644 --- a/spec/ruby/language/regexp/escapes_spec.rb +++ b/spec/ruby/language/regexp/escapes_spec.rb @@ -78,4 +78,20 @@ describe "Regexps with escape characters" do # \M-x meta (x|0x80) (character code point value) # \M-\C-x meta control char (character code point value) end + + it "handles three digit octal escapes starting with 0" do + /[\000-\b]/.match("\x00")[0].should == "\x00" + end + + it "handles control escapes with \\C-x syntax" do + /\C-*\C-J\C-j/.match("\n\n\n")[0].should == "\n\n\n" + end + + it "supports the \\K keep operator" do + /a\Kb/.match("ab")[0].should == "b" + end + + it "supports the \\R line break escape" do + /\R/.match("\n")[0].should == "\n" + end end diff --git a/spec/ruby/language/regexp/grouping_spec.rb b/spec/ruby/language/regexp/grouping_spec.rb index 8806d06746..2fecf2d2cb 100644 --- a/spec/ruby/language/regexp/grouping_spec.rb +++ b/spec/ruby/language/regexp/grouping_spec.rb @@ -20,4 +20,9 @@ describe "Regexps with grouping" do # Parsing precedence /(?:xdigit:)/.match("xdigit:").to_a.should == ["xdigit:"] end + + it "group names cannot start with digits or minus" do + -> { Regexp.new("(?<1a>a)") }.should raise_error(RegexpError) + -> { Regexp.new("(?<-a>a)") }.should raise_error(RegexpError) + end end diff --git a/spec/ruby/language/regexp/repetition_spec.rb b/spec/ruby/language/regexp/repetition_spec.rb index 7bb767ccaf..295b3bf553 100644 --- a/spec/ruby/language/regexp/repetition_spec.rb +++ b/spec/ruby/language/regexp/repetition_spec.rb @@ -45,4 +45,87 @@ describe "Regexps with repetition" do /a?/.match("aaa").to_a.should == ["a"] /a?/.match("bbb").to_a.should == [""] end + + it "handles incomplete range quantifiers" do + /a{}/.match("a{}")[0].should == "a{}" + /a{,}/.match("a{,}")[0].should == "a{,}" + /a{1/.match("a{1")[0].should == "a{1" + /a{1,2/.match("a{1,2")[0].should == "a{1,2" + /a{,5}/.match("aaa")[0].should == "aaa" + end + + it "lets us use quantifiers on assertions" do + /a^?b/.match("ab")[0].should == "ab" + /a$?b/.match("ab")[0].should == "ab" + /a\A?b/.match("ab")[0].should == "ab" + /a\Z?b/.match("ab")[0].should == "ab" + /a\z?b/.match("ab")[0].should == "ab" + /a\G?b/.match("ab")[0].should == "ab" + /a\b?b/.match("ab")[0].should == "ab" + /a\B?b/.match("ab")[0].should == "ab" + /a(?=c)?b/.match("ab")[0].should == "ab" + /a(?!=b)?b/.match("ab")[0].should == "ab" + /a(?<=c)?b/.match("ab")[0].should == "ab" + /a(?<!a)?b/.match("ab")[0].should == "ab" + end + + it "does not delete optional assertions" do + /(?=(a))?/.match("a").to_a.should == [ "", "a" ] + end + + it "supports nested quantifiers" do + suppress_warning do + eval <<-RUBY + /a***/.match("aaa")[0].should == "aaa" + + # a+?* should not be reduced, it should be equivalent to (a+?)* + # NB: the capture group prevents regex engines from reducing the two quantifiers + # https://bugs.ruby-lang.org/issues/17341 + /a+?*/.match("")[0].should == "" + /(a+?)*/.match("")[0].should == "" + + /a+?*/.match("a")[0].should == "a" + /(a+?)*/.match("a")[0].should == "a" + + ruby_bug '#17341', ''...'3.0' do + /a+?*/.match("aa")[0].should == "aa" + end + /(a+?)*/.match("aa")[0].should == "aa" + + # a+?+ should not be reduced, it should be equivalent to (a+?)+ + # https://bugs.ruby-lang.org/issues/17341 + /a+?+/.match("").should == nil + /(a+?)+/.match("").should == nil + + /a+?+/.match("a")[0].should == "a" + /(a+?)+/.match("a")[0].should == "a" + + ruby_bug '#17341', ''...'3.0' do + /a+?+/.match("aa")[0].should == "aa" + end + /(a+?)+/.match("aa")[0].should == "aa" + + # both a**? and a+*? should be equivalent to (a+)?? + # this quantifier would rather match nothing, but if that's not possible, + # it will greedily take everything + /a**?/.match("")[0].should == "" + /(a*)*?/.match("")[0].should == "" + /a+*?/.match("")[0].should == "" + /(a+)*?/.match("")[0].should == "" + /(a+)??/.match("")[0].should == "" + + /a**?/.match("aaa")[0].should == "" + /(a*)*?/.match("aaa")[0].should == "" + /a+*?/.match("aaa")[0].should == "" + /(a+)*?/.match("aaa")[0].should == "" + /(a+)??/.match("aaa")[0].should == "" + + /b.**?b/.match("baaabaaab")[0].should == "baaabaaab" + /b(.*)*?b/.match("baaabaaab")[0].should == "baaabaaab" + /b.+*?b/.match("baaabaaab")[0].should == "baaabaaab" + /b(.+)*?b/.match("baaabaaab")[0].should == "baaabaaab" + /b(.+)??b/.match("baaabaaab")[0].should == "baaabaaab" + RUBY + end + end end diff --git a/spec/ruby/language/regexp/subexpression_call_spec.rb b/spec/ruby/language/regexp/subexpression_call_spec.rb new file mode 100644 index 0000000000..16b64cb327 --- /dev/null +++ b/spec/ruby/language/regexp/subexpression_call_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' +require_relative '../fixtures/classes' + +describe "Regexps with subexpression calls" do + it "allows numeric subexpression calls" do + /(a)\g<1>/.match("aa").to_a.should == [ "aa", "a" ] + end + + it "treats subexpression calls as distinct from simple back-references" do + # Back-references only match a string which is equal to the original captured string. + /(?<three_digits>[0-9]{3})-\k<three_digits>/.match("123-123")[0].should == "123-123" + /(?<three_digits>[0-9]{3})-\k<three_digits>/.match("123-456").should == nil + # However, subexpression calls reuse the previous expression and can match a different + # string. + /(?<three_digits>[0-9]{3})-\g<three_digits>/.match("123-456")[0].should == "123-456" + end + + it "allows recursive subexpression calls" do + # This pattern matches well-nested parenthesized expression. + parens = /^ (?<parens> (?: \( \g<parens> \) | [^()] )* ) $/x + parens.match("((a)(b))c(d)")[0].should == "((a)(b))c(d)" + parens.match("((a)(b)c(d)").should == nil + end + + it "allows access to back-references from the current level" do + # Using \\k<first_char-0> accesses the last value captured in first_char + # on the current stack level. + mirror = /^ (?<mirror> (?: (?<first_char>.) \g<mirror> \k<first_char-0> )? ) $/x + mirror.match("abccba")[0].should == "abccba" + mirror.match("abccbd").should == nil + + # OTOH, using \\k<first_char> accesses the last value captured in first_char, + # regardless of the stack level. Therefore, it can't be used to implement + # the mirror language. + broken_mirror = /^ (?<mirror> (?: (?<first_char>.) \g<mirror> \k<first_char> )? ) $/x + broken_mirror.match("abccba").should == nil + # This matches because the 'c' is captured in first_char and that value is + # then used for all subsequent back-references, regardless of nesting. + broken_mirror.match("abcccc")[0].should == "abcccc" + end + + it "allows + and - in group names and referential constructs that don't use levels, i.e. subexpression calls" do + /(?<a+>a)\g<a+>/.match("aa").to_a.should == [ "aa", "a" ] + /(?<a+b>a)\g<a+b>/.match("aa").to_a.should == [ "aa", "a" ] + /(?<a+1>a)\g<a+1>/.match("aa").to_a.should == [ "aa", "a" ] + /(?<a->a)\g<a->/.match("aa").to_a.should == [ "aa", "a" ] + /(?<a-b>a)\g<a-b>/.match("aa").to_a.should == [ "aa", "a" ] + /(?<a-1>a)\g<a-1>/.match("aa").to_a.should == [ "aa", "a" ] + end +end diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb index 91ce9726d5..7e9370d6f9 100644 --- a/spec/ruby/language/singleton_class_spec.rb +++ b/spec/ruby/language/singleton_class_spec.rb @@ -74,7 +74,7 @@ describe "A singleton class" do end it "doesn't have singleton class" do - -> { bignum_value.singleton_class.superclass.should == Integer }.should raise_error(TypeError) + -> { bignum_value.singleton_class }.should raise_error(TypeError) end end |