+=head2 C<is_deeply $got, $expected [, $desc ]>
+
+=cut
+
+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 = Scalar::Util::reftype($y);
+ return 0 if Scalar::Util::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;
+
+ if ($ry eq 'ARRAY') {
+ if ($#$x == $#$y) {
+ _deep_check($x->[$_], $y->[$_]) or return 0 for 0 .. $#$y;
+ return 1;
+ }
+ } elsif ($ry eq 'HASH') {
+ if (keys(%$x) == keys(%$y)) {
+ (exists $x->{$_} and _deep_check($x->{$_}, $y->{$_}))
+ or return 0 for keys %$y;
+ return 1;
+ }
+ } elsif ($ry eq 'SCALAR' or $ry eq 'REF') {
+ return _deep_check($$x, $$y);
+ }
+
+ return 0;
+};
+
+sub is_deeply {
+ @_ = (
+ &_deep_check,
+ $_[2],
+ );
+ goto &ok;
+}
+