This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
t/op/local.t: tests for RT #7615
[perl5.git] / t / op / smartmatch.t
index ed41aaa..ca019fd 100644 (file)
@@ -1,24 +1,27 @@
 #!./perl
 
 BEGIN {
-    chdir 't';
-    @INC = '../lib';
+    chdir 't' if -d 't';
     require './test.pl';
+    set_up_inc('../lib');
 }
 use strict;
+use warnings;
+no warnings 'uninitialized';
+no warnings 'experimental::smartmatch';
+
+++$|;
 
 use Tie::Array;
 use Tie::Hash;
 
-# The feature mechanism is tested in t/lib/feature/smartmatch:
-# This file tests the semantics of the operator, without worrying
-# about feature issues such as scoping etc.
-
 # Predeclare vars used in the tests:
-my $deep1 = []; push @$deep1, \$deep1;
-my $deep2 = []; push @$deep2, \$deep2;
+my @empty;
+my %empty;
+my @sparse; $sparse[2] = 2;
 
-{my $const = "a constant"; sub a_const () {$const}}
+my $deep1 = []; push @$deep1, $deep1;
+my $deep2 = []; push @$deep2, $deep2;
 
 my @nums = (1..10);
 tie my @tied_nums, 'Tie::StdArray';
@@ -28,147 +31,499 @@ my %hash = (foo => 17, bar => 23);
 tie my %tied_hash, 'Tie::StdHash';
 %tied_hash = %hash;
 
-# Load and run the tests
-my @tests = map [chomp and split /\t+/, $_, 3], grep !/^#/ && /\S/, <DATA>;
-plan tests => 2 * @tests;
-
-for my $test (@tests) {
-    my ($yn, $left, $right) = @$test;
+{
+    package Test::Object::NoOverload;
+    sub new { bless { key => 1 } }
+}
 
-    match_test($yn, $left, $right);
-    match_test($yn, $right, $left);
+{
+    package Test::Object::StringOverload;
+    use overload '""' => sub { "object" }, fallback => 1;
+    sub new { bless { key => 1 } }
 }
 
-sub match_test {
-    my ($yn, $left, $right) = @_;
+{
+    package Test::Object::WithOverload;
+    sub new { bless { key => ($_[1] // 'magic') } }
+    use overload '~~' => sub {
+       my %hash = %{ $_[0] };
+       if ($_[2]) { # arguments reversed ?
+           return $_[1] eq reverse $hash{key};
+       }
+       else {
+           return $_[1] eq $hash{key};
+       }
+    };
+    use overload '""' => sub { "stringified" };
+    use overload 'eq' => sub {"$_[0]" eq "$_[1]"};
+}
 
-    die "Bad test spec: ($yn, $left, $right)"
-       unless $yn eq "" || $yn eq "!";
-    
-    my $tstr = "$left ~~ $right";
-    
-    my $res;
-    $res = eval $tstr // "";   #/ <- fix syntax colouring
+our $ov_obj = Test::Object::WithOverload->new;
+our $ov_obj_2 = Test::Object::WithOverload->new("object");
+our $obj = Test::Object::NoOverload->new;
+our $str_obj = Test::Object::StringOverload->new;
 
-    die $@ if $@ ne "";
-    ok( ($yn =~ /!/ xor $res), "$tstr: $res");
+my %refh;
+unless (is_miniperl()) {
+    require Tie::RefHash;
+    tie %refh, 'Tie::RefHash';
+    $refh{$ov_obj} = 1;
 }
 
+my @keyandmore = qw(key and more);
+my @fooormore = qw(foo or more);
+my %keyandmore = map { $_ => 0 } @keyandmore;
+my %fooormore = map { $_ => 0 } @fooormore;
+
+# Load and run the tests
+plan tests => 349+2;
+
+while (<DATA>) {
+  SKIP: {
+    next if /^#/ || !/\S/;
+    chomp;
+    my ($yn, $left, $right, $note) = split /\t+/;
 
+    local $::TODO = $note =~ /TODO/;
+
+    die "Bad test spec: ($yn, $left, $right)" if $yn =~ /[^!@=]/;
+
+    my $tstr = "$left ~~ $right";
+
+    test_again:
+    my $res;
+    if ($note =~ /NOWARNINGS/) {
+       $res = eval "no warnings; $tstr";
+    }
+    else {
+       skip_if_miniperl("Doesn't work with miniperl", $yn =~ /=/ ? 2 : 1)
+           if $note =~ /MINISKIP/;
+       $res = eval $tstr;
+    }
+
+    chomp $@;
+
+    if ( $yn =~ /@/ ) {
+       ok( $@ ne '', "$tstr dies" )
+           and print "# \$\@ was: $@\n";
+    } else {
+       my $test_name = $tstr . ($yn =~ /!/ ? " does not match" : " matches");
+       if ( $@ ne '' ) {
+           fail($test_name);
+           print "# \$\@ was: $@\n";
+       } else {
+           ok( ($yn =~ /!/ xor $res), $test_name );
+       }
+    }
+
+    if ( $yn =~ s/=// ) {
+       $tstr = "$right ~~ $left";
+       goto test_again;
+    }
+  }
+}
 
 sub foo {}
-sub bar {2}
-sub fatal {die}
+sub bar {42}
+sub gorch {42}
+sub fatal {die "fatal sub\n"}
+
+# to test constant folding
+sub FALSE() { 0 }
+sub TRUE() { 1 }
+sub NOT_DEF() { undef }
+
+{
+  # [perl #123860]
+  # this can but might not crash
+  # This can but might not crash
+  #
+  # The second smartmatch would leave a &PL_sv_no on the stack for
+  # each key it checked in %!, this could then cause various types of
+  # crash or assertion failure.
+  #
+  # This isn't guaranteed to crash, but if the stack issue is
+  # re-introduced it will probably crash in one of the many smoke
+  # builds.
+  fresh_perl_is('print (q(x) ~~ q(x)) | (/x/ ~~ %!)', "1",
+               { switches => [ "-MErrno", "-M-warnings=experimental::smartmatch" ] },
+                "don't fill the stack with rubbish");
+}
 
-sub a_const() {die if @_; "a constant"}
-sub b_const() {die if @_; "a constant"}
+{
+    # [perl #123860] continued;
+    # smartmatch was failing to SPAGAIN after pushing an SV and calling
+    # pp_match, which may have resulted in the stack being realloced
+    # in the meantime. Test this by filling the stack with pregressively
+    # larger amounts of data. At some point the stack will get realloced.
+    my @a = qw(x);
+    my %h = qw(x 1);
+    my @args;
+    my $x = 1;
+    my $bad = -1;
+    for (1..1000)  {
+        push @args, $_;
+        my $exp_n  = join '-',  (@args, $x == 0);
+        my $exp_y  = join '-',  (@args, $x == 1);
+
+        my $got_an = join '-',  (@args, (/X/ ~~ @a));
+        my $got_ay = join '-',  (@args, (/x/ ~~ @a));
+        my $got_hn = join '-',  (@args, (/X/ ~~ %h));
+        my $got_hy = join '-',  (@args, (/x/ ~~ %h));
+
+        if (   $exp_n ne $got_an || $exp_n ne $got_hn
+            || $exp_y ne $got_ay || $exp_y ne $got_hy
+        ) {
+            $bad = $_;
+            last;
+        }
+    }
+    is($bad, -1, "RT 123860: stack realloc");
+}
 
-__DATA__
-# CODE ref against argument
-#  - arg is code ref
-       \&foo           \&foo
-!      \&foo           sub {}
-!      \&foo           \&bar
 
-# - arg is not code ref
+# Prefix character :
+#   - expected to match
+# ! - expected to not match
+# @ - expected to be a compilation failure
+# = - expected to match symmetrically (runs test twice)
+# Data types to test :
+#   undef
+#   Object-overloaded
+#   Object
+#   Coderef
+#   Hash
+#   Hashref
+#   Array
+#   Arrayref
+#   Tied arrays and hashes
+#   Arrays that reference themselves
+#   Regex (// and qr//)
+#   Range
+#   Num
+#   Str
+# Other syntactic items of interest:
+#   Constants
+#   Values returned by a sub call
+__DATA__
+# Any ~~ undef
+!      $ov_obj         undef
+!      $obj            undef
+!      sub {}          undef
+!      %hash           undef
+!      \%hash          undef
+!      {}              undef
+!      @nums           undef
+!      \@nums          undef
+!      []              undef
+!      %tied_hash      undef
+!      @tied_nums      undef
+!      $deep1          undef
+!      /foo/           undef
+!      qr/foo/         undef
+!      21..30          undef
+!      189             undef
+!      "foo"           undef
+!      ""              undef
+!      !1              undef
+       undef           undef
+       (my $u)         undef
+       NOT_DEF         undef
+       &NOT_DEF        undef
+
+# Any ~~ object overloaded
+!      \&fatal         $ov_obj
+       'cigam'         $ov_obj
+!      'cigam on'      $ov_obj
+!      ['cigam']       $ov_obj
+!      ['stringified'] $ov_obj
+!      { cigam => 1 }  $ov_obj
+!      { stringified => 1 }    $ov_obj
+!      $obj            $ov_obj
+!      undef           $ov_obj
+
+# regular object
+@      $obj            $obj
+@      $ov_obj         $obj
+=@     \&fatal         $obj
+@      \&FALSE         $obj
+@      \&foo           $obj
+@      sub { 1 }       $obj
+@      sub { 0 }       $obj
+@      %keyandmore     $obj
+@      {"key" => 1}    $obj
+@      @fooormore      $obj
+@      ["key" => 1]    $obj
+@      /key/           $obj
+@      qr/key/         $obj
+@      "key"           $obj
+@      FALSE           $obj
+
+# regular object with "" overload
+@      $obj            $str_obj
+=@     \&fatal         $str_obj
+@      \&FALSE         $str_obj
+@      \&foo           $str_obj
+@      sub { 1 }       $str_obj
+@      sub { 0 }       $str_obj
+@      %keyandmore     $str_obj
+@      {"object" => 1} $str_obj
+@      @fooormore      $str_obj
+@      ["object" => 1] $str_obj
+@      /object/        $str_obj
+@      qr/object/      $str_obj
+@      "object"        $str_obj
+@      FALSE           $str_obj
+# Those will treat the $str_obj as a string because of fallback:
+
+# object (overloaded or not) ~~ Any
+       $obj            qr/NoOverload/
+       $ov_obj         qr/^stringified$/
+=      "$ov_obj"       "stringified"
+=      "$str_obj"      "object"
+!=     $ov_obj         "stringified"
+       $str_obj        "object"
+       $ov_obj         'magic'
+!      $ov_obj         'not magic'
+
+# ~~ Coderef
+       sub{0}          sub { ref $_[0] eq "CODE" }
+       %fooormore      sub { $_[0] =~ /^(foo|or|more)$/ }
+!      %fooormore      sub { $_[0] =~ /^(foo|or|less)$/ }
+       \%fooormore     sub { $_[0] =~ /^(foo|or|more)$/ }
+!      \%fooormore     sub { $_[0] =~ /^(foo|or|less)$/ }
+       +{%fooormore}   sub { $_[0] =~ /^(foo|or|more)$/ }
+!      +{%fooormore}   sub { $_[0] =~ /^(foo|or|less)$/ }
+       @fooormore      sub { $_[0] =~ /^(foo|or|more)$/ }
+!      @fooormore      sub { $_[0] =~ /^(foo|or|less)$/ }
+       \@fooormore     sub { $_[0] =~ /^(foo|or|more)$/ }
+!      \@fooormore     sub { $_[0] =~ /^(foo|or|less)$/ }
+       [@fooormore]    sub { $_[0] =~ /^(foo|or|more)$/ }
+!      [@fooormore]    sub { $_[0] =~ /^(foo|or|less)$/ }
+       %fooormore      sub{@_==1}
+       @fooormore      sub{@_==1}
+       "foo"           sub { $_[0] =~ /^(foo|or|more)$/ }
+!      "more"          sub { $_[0] =~ /^(foo|or|less)$/ }
+       /fooormore/     sub{ref $_[0] eq 'Regexp'}
+       qr/fooormore/   sub{ref $_[0] eq 'Regexp'}
        1               sub{shift}
 !      0               sub{shift}
-       1               sub{scalar @_}
-       []              \&bar
-       {}              \&bar
+!      undef           sub{shift}
+       undef           sub{not shift}
+       NOT_DEF         sub{not shift}
+       &NOT_DEF        sub{not shift}
+       FALSE           sub{not shift}
+       [1]             \&bar
+       {a=>1}          \&bar
        qr//            \&bar
-
-# - null-prototyped subs
-       a_const         "a constant"
-       a_const         a_const
-       a_const         b_const
+!      [1]             \&foo
+!      {a=>1}          \&foo
+       $obj            sub { ref($_[0]) =~ /NoOverload/ }
+       $ov_obj         sub { ref($_[0]) =~ /WithOverload/ }
+# empty stuff matches, because the sub is never called:
+       []              \&foo
+       {}              \&foo
+       @empty          \&foo
+       %empty          \&foo
+!      qr//            \&foo
+!      undef           \&foo
+       undef           \&bar
+@      undef           \&fatal
+@      1               \&fatal
+@      [1]             \&fatal
+@      {a=>1}          \&fatal
+@      "foo"           \&fatal
+@      qr//            \&fatal
+# sub is not called on empty hashes / arrays
+       []              \&fatal
+       +{}             \&fatal
+       @empty          \&fatal
+       %empty          \&fatal
+# sub is not special on the left
+       sub {0}         qr/^CODE/
+       sub {0}         sub { ref shift eq "CODE" }
 
 # HASH ref against:
 #   - another hash ref
        {}              {}
-     {}              {1 => 2}
+=!     {}              {1 => 2}
        {1 => 2}        {1 => 2}
        {1 => 2}        {1 => 3}
-     {1 => 2}        {2 => 3}
-       \%main::        {map {$_ => 'x'} keys %main::}
+=!     {1 => 2}        {2 => 3}
+=      \%main::        {map {$_ => 'x'} keys %main::}
 
 #  - tied hash ref
-       \%hash          \%tied_hash
+=      \%hash          \%tied_hash
        \%tied_hash     \%tied_hash
+!=     {"a"=>"b"}      \%tied_hash
+=      %hash           %tied_hash
+       %tied_hash      %tied_hash
+!=     {"a"=>"b"}      %tied_hash
+       $ov_obj         %refh           MINISKIP
+!      "$ov_obj"       %refh           MINISKIP
+       [$ov_obj]       %refh           MINISKIP
+!      ["$ov_obj"]     %refh           MINISKIP
+       %refh           %refh           MINISKIP
 
 #  - an array ref
-       \%::            [keys %main::]
-!      \%::            []
-       {"" => 1}       [undef]
-       { foo => 1 }    ["foo"]
-       { foo => 1 }    ["foo", "bar"]
-       \%hash          ["foo", "bar"]
-       \%hash          ["foo"]
-!      \%hash          ["quux"]
-       \%hash          [qw(foo quux)]
+#  (since this is symmetrical, tests as well hash~~array)
+=      [keys %main::]  \%::
+=      [qw[STDIN STDOUT]]      \%::
+=!     []              \%::
+=!     [""]            {}
+=!     []              {}
+=!     @empty          {}
+=      [undef]         {"" => 1}
+=      [""]            {"" => 1}
+=      ["foo"]         { foo => 1 }
+=      ["foo", "bar"]  { foo => 1 }
+=      ["foo", "bar"]  \%hash
+=      ["foo"]         \%hash
+=!     ["quux"]        \%hash
+=      [qw(foo quux)]  \%hash
+=      @fooormore      { foo => 1, or => 2, more => 3 }
+=      @fooormore      %fooormore
+=      @fooormore      \%fooormore
+=      \@fooormore     %fooormore
 
 #  - a regex
-       {foo => 1}      qr/^(fo[ox])$/
-!      +{0..100}       qr/[13579]$/
-
-#  - a string
-       +{foo => 1, bar => 2}   "foo"
-!      +{foo => 1, bar => 2}   "baz"
-
+=      qr/^(fo[ox])$/          {foo => 1}
+=      /^(fo[ox])$/            %fooormore
+=!     qr/[13579]$/            +{0..99}
+=!     qr/a*/                  {}
+=      qr/a*/                  {b=>2}
+=      qr/B/i                  {b=>2}
+=      /B/i                    {b=>2}
+=!     qr/a+/                  {b=>2}
+=      qr/^à/                 {"à"=>2}
+
+#  - a scalar
+       "foo"           +{foo => 1, bar => 2}
+       "foo"           %fooormore
+!      "baz"           +{foo => 1, bar => 2}
+!      "boz"           %fooormore
+!      1               +{foo => 1, bar => 2}
+!      1               %fooormore
+       1               { 1 => 3 }
+       1.0             { 1 => 3 }
+!      "1.0"           { 1 => 3 }
+!      "1.0"           { 1.0 => 3 }
+       "1.0"           { "1.0" => 3 }
+       "à"            { "à" => "À" }
+
+#  - undef
+!      undef           { hop => 'zouu' }
+!      undef           %hash
+!      undef           +{"" => "empty key"}
+!      undef           {}
 
 # ARRAY ref against:
 #  - another array ref
-       []              []
-!      []              [1]
+       []                      []
+=!     []                      [1]
        [["foo"], ["bar"]]      [qr/o/, qr/a/]
+!      [["foo"], ["bar"]]      [qr/ARRAY/, qr/ARRAY/]
        ["foo", "bar"]          [qr/o/, qr/a/]
+!      [qr/o/, qr/a/]          ["foo", "bar"]
+       ["foo", "bar"]          [["foo"], ["bar"]]
 !      ["foo", "bar"]          [qr/o/, "foo"]
-       $deep1          $deep1
-!      $deep1          $deep2
-
-       \@nums          \@tied_nums
+       ["foo", undef, "bar"]   [qr/o/, undef, "bar"]
+!      ["foo", undef, "bar"]   [qr/o/, "",    "bar"]
+!      ["foo", "", "bar"]      [qr/o/, undef, "bar"]
+       $deep1                  $deep1
+       @$deep1                 @$deep1
+!      $deep1                  $deep2
+
+=      \@nums                  \@tied_nums
+=      @nums                   \@tied_nums
+=      \@nums                  @tied_nums
+=      @nums                   @tied_nums
+
+#  - an object
+!      $obj            @fooormore
+       $obj            [sub{ref shift}]
 
 #  - a regex
-       [qw(foo bar baz quux)]  qr/x/
-!      [qw(foo bar baz quux)]  qr/y/
+=      qr/x/           [qw(foo bar baz quux)]
+=!     qr/y/           [qw(foo bar baz quux)]
+=      /x/             [qw(foo bar baz quux)]
+=!     /y/             [qw(foo bar baz quux)]
+=      /FOO/i          @fooormore
+=!     /bar/           @fooormore
 
 # - a number
-       [qw(1foo 2bar)]         2
+       2               [qw(1.00 2.00)]
+       2               [qw(foo 2)]
+       2.0_0e+0        [qw(foo 2)]
+!      2               [qw(1foo bar2)]
 
 # - a string
-!      [qw(1foo 2bar)]         "2"
+!      "2"             [qw(1foo 2bar)]
+       "2bar"          [qw(1foo 2bar)]
+
+# - undef
+       undef           [1, 2, undef, 4]
+!      undef           [1, 2, [undef], 4]
+!      undef           @fooormore
+       undef           @sparse
+       undef           [undef]
+!      0               [undef]
+!      ""              [undef]
+!      undef           [0]
+!      undef           [""]
+
+# - nested arrays and ~~ distributivity
+       11              [[11]]
+!      11              [[12]]
+       "foo"           [{foo => "bar"}]
+!      "bar"           [{foo => "bar"}]
 
 # Number against number
        2               2
+       20              2_0
 !      2               3
+       0               FALSE
+       3-2             TRUE
+!      undef           0
+!      (my $u)         0
 
 # Number against string
-       2               "2"
-       2               "2.0"
+=      2               "2"
+=      2               "2.0"
 !      2               "2bananas"
-!      2_3             "2_3"
+!=     2_3             "2_3"           NOWARNINGS
+       FALSE           "0"
+!      undef           "0"
+!      undef           ""
 
 # Regex against string
-       qr/x/           "x"
-!      qr/y/           "x"
+       "x"             qr/x/
+!      "x"             qr/y/
 
 # Regex against number
        12345           qr/3/
+!      12345           qr/7/
 
+# array/hash against string
+       @fooormore      "".\@fooormore
+!      @keyandmore     "".\@fooormore
+       %fooormore      "".\%fooormore
+!      %keyandmore     "".\%fooormore
 
 # Test the implicit referencing
-       @nums           7
+       7               @nums
        @nums           \@nums
 !      @nums           \\@nums
        @nums           [1..10]
 !      @nums           [0..9]
 
-       %hash           "foo"
-       %hash           /bar/
-       %hash           [qw(bar)]
-!      %hash           [qw(a b c)]
+       "foo"           %hash
+       /bar/           %hash
+       [qw(bar)]       %hash
+!      [qw(a b c)]     %hash
        %hash           %hash
-       %hash           {%hash}
+       %hash           +{%hash}
+       %hash           \%hash
        %hash           %tied_hash
        %tied_hash      %tied_hash
        %hash           { foo => 5, bar => 10 }
@@ -177,3 +532,42 @@ __DATA__
        @nums           {  1, '',  2, '' }
        @nums           {  1, '', 12, '' }
 !      @nums           { 11, '', 12, '' }
+
+# array slices
+       @nums[0..-1]    []
+       @nums[0..0]     [1]
+!      @nums[0..1]     [0..2]
+       @nums[0..4]     [1..5]
+
+!      undef           @nums[0..-1]
+       1               @nums[0..0]
+       2               @nums[0..1]
+!      @nums[0..1]     2
+
+       @nums[0..1]     @nums[0..1]
+
+# hash slices
+       @keyandmore{qw(not)}            [undef]
+       @keyandmore{qw(key)}            [0]
+
+       undef                           @keyandmore{qw(not)}
+       0                               @keyandmore{qw(key and more)}
+!      2                               @keyandmore{qw(key and)}
+
+       @fooormore{qw(foo)}             @keyandmore{qw(key)}
+       @fooormore{qw(foo or more)}     @keyandmore{qw(key and more)}
+
+# UNDEF
+!      3               undef
+!      1               undef
+!      []              undef
+!      {}              undef
+!      \%::main        undef
+!      [1,2]           undef
+!      %hash           undef
+!      @nums           undef
+!      "foo"           undef
+!      ""              undef
+!      !1              undef
+!      \&foo           undef
+!      sub { }         undef