Restore the value of $@ from before the local
Yuval Kogman [Fri, 11 Sep 2009 17:52:41 +0000 (02:52 +0900)]
This allows exception objects to be constructed with the previous value
of $@, but that behavior would rely on Try::Tiny

lib/Try/Tiny.pm
t/basic.t

index dd6d466..e3fdf8d 100644 (file)
@@ -23,6 +23,9 @@ sub try (&;$) {
        # to $failed
        my $wantarray = wantarray;
 
+       # save the value of $@ so we can set $@ back to it in the begining of the eval
+       my $prev_error = $@;
+
        my ( @ret, $error, $failed );
 
        # FIXME consider using local $SIG{__DIE__} to accumilate all errors. It's
@@ -37,6 +40,7 @@ sub try (&;$) {
                # failed will be true if the eval dies, because 1 will not be returned
                # from the eval body
                $failed = not eval {
+                       $@ = $prev_error;
 
                        # evaluate the try block in the correct context
                        if ( $wantarray ) {
@@ -180,6 +184,13 @@ not yet handled.
 C<$@> must be properly localized before invoking C<eval> in order to avoid this
 issue.
 
+More specifically, C<$@> is clobbered at the begining of the C<eval>, which
+also makes it impossible to capture the previous error before you die (for
+instance when making exception objects with error stacks).
+
+For this reason C<try> will actually set C<$@> to its previous value (before
+the localization) in the begining of the C<eval> block.
+
 =head2 Localizing $@ silently masks errors
 
 Inside an eval block C<die> behaves sort of like:
index ebc4106..7f1471c 100644 (file)
--- a/t/basic.t
+++ b/t/basic.t
@@ -3,7 +3,7 @@
 use strict;
 #use warnings;
 
-use Test::More tests => 19;
+use Test::More tests => 21;
 
 BEGIN { use_ok 'Try::Tiny' };
 
@@ -39,6 +39,8 @@ sub throws_ok (&$$) {
 }
 
 
+my $prev;
+
 lives_ok {
        try {
                die "foo";
@@ -125,3 +127,25 @@ sub Evil::new { bless { }, $_[0] }
        is( $@, "magic", '$@ untouched' );
        is( $_, "other magic", '$_ untouched' );
 }
+
+{
+       my $caught;
+
+       {
+               local $@;
+
+               eval { die "bar\n" };
+
+               is( $@, "bar\n", 'previous value of $@' );
+
+               try {
+                       die {
+                               prev => $@,
+                       }
+               } catch {
+                       $caught = $_;
+               }
+       }
+
+       is_deeply( $caught, { prev => "bar\n" }, 'previous value of $@ available for capture' );
+}