+=head2 C<is_deeply>
+
+ is_deeply $got, $expected;
+ is_deeply $got, $expected, $desc;
+
+See L<Test::More/is_deeply>.
+
+=cut
+
+BEGIN {
+ local $@;
+ if (eval { require Scalar::Util; 1 }) {
+ *_reftype = \&Scalar::Util::reftype;
+ } else {
+ # Stolen from Scalar::Util::PP
+ require B;
+ my %tmap = qw<
+ B::NULL SCALAR
+
+ B::HV HASH
+ B::AV ARRAY
+ B::CV CODE
+ B::IO IO
+ B::GV GLOB
+ B::REGEXP REGEXP
+ >;
+ *_reftype = sub ($) {
+ my $r = shift;
+
+ return undef unless length ref $r;
+
+ my $t = ref B::svref_2object($r);
+
+ return exists $tmap{$t} ? $tmap{$t}
+ : length ref $$r ? 'REF'
+ : 'SCALAR'
+ }
+ }
+}
+
+sub _deep_ref_check {
+ my ($x, $y, $ry) = @_;
+
+ no warnings qw<numeric uninitialized>;
+
+ if ($ry eq 'ARRAY') {
+ return 0 unless $#$x == $#$y;
+
+ my ($ex, $ey);
+ for (0 .. $#$y) {
+ $ex = $x->[$_];
+ $ey = $y->[$_];
+
+ # Inline the beginning of _deep_check
+ return 0 if defined $ex xor defined $ey;
+
+ next if not(ref $ex xor ref $ey) and $ex eq $ey;
+
+ $ry = _reftype($ey);
+ return 0 if _reftype($ex) ne $ry;
+
+ return 0 unless $ry and _deep_ref_check($ex, $ey, $ry);
+ }
+
+ return 1;
+ } elsif ($ry eq 'HASH') {
+ return 0 unless keys(%$x) == keys(%$y);
+
+ my ($ex, $ey);
+ for (keys %$y) {
+ return 0 unless exists $x->{$_};
+ $ex = $x->{$_};
+ $ey = $y->{$_};
+
+ # Inline the beginning of _deep_check
+ return 0 if defined $ex xor defined $ey;
+
+ next if not(ref $ex xor ref $ey) and $ex eq $ey;
+
+ $ry = _reftype($ey);
+ return 0 if _reftype($ex) ne $ry;
+
+ return 0 unless $ry and _deep_ref_check($ex, $ey, $ry);
+ }
+
+ return 1;
+ } elsif ($ry eq 'SCALAR' or $ry eq 'REF') {
+ return _deep_check($$x, $$y);
+ }
+
+ return 0;
+}
+
+sub _deep_check {
+ my ($x, $y) = @_;
+
+ no warnings qw<numeric uninitialized>;
+
+ return 0 if defined $x xor defined $y;
+
+ # Try object identity/eq overloading first. It also covers the case where
+ # $x and $y are both undefined.
+ # If either $x or $y is overloaded but none has eq overloading, the test will
+ # break at that point.
+ return 1 if not(ref $x xor ref $y) and $x eq $y;
+
+ # Test::More::is_deeply happily breaks encapsulation if the objects aren't
+ # overloaded.
+ my $ry = _reftype($y);
+ return 0 if _reftype($x) ne $ry;
+
+ # Shortcut if $x and $y are both not references and failed the previous
+ # $x eq $y test.
+ return 0 unless $ry;
+
+ # We know that $x and $y are both references of type $ry, without overloading.
+ _deep_ref_check($x, $y, $ry);
+}
+
+sub is_deeply {
+ @_ = (
+ &_deep_check,
+ $_[2],
+ );
+ goto &ok;
+}
+