Make sure users are properly notified of (useless) exceptions in finally blocks
[p5sagit/Try-Tiny.git] / lib / Try / Tiny.pm
index 9d46a76..3331660 100644 (file)
@@ -28,16 +28,19 @@ sub try (&;@) {
   # find labeled blocks in the argument list.
   # catch and finally tag the blocks by blessing a scalar reference to them.
   foreach my $code_ref (@code_refs) {
-    next unless $code_ref;
 
-    my $ref = ref($code_ref);
-
-    if ( $ref eq 'Try::Tiny::Catch' ) {
+    if ( ref($code_ref) eq 'Try::Tiny::Catch' ) {
+      croak 'A try() may not be followed by multiple catch() blocks'
+        if $catch;
       $catch = ${$code_ref};
-    } elsif ( $ref eq 'Try::Tiny::Finally' ) {
+    } elsif ( ref($code_ref) eq 'Try::Tiny::Finally' ) {
       push @finally, ${$code_ref};
     } else {
-      confess("Unknown code ref type given '${ref}'. Check your usage & try again");
+      croak(
+        'try() encountered an unexpected argument ('
+      . ( defined $code_ref ? $code_ref : 'undef' )
+      . ') - perhaps a missing semi-colon before or'
+      );
     }
   }
 
@@ -101,6 +104,8 @@ sub try (&;@) {
 sub catch (&;@) {
   my ( $block, @rest ) = @_;
 
+  croak 'Useless bare catch()' unless defined wantarray;
+
   return (
     bless(\$block, 'Try::Tiny::Catch'),
     @rest,
@@ -110,6 +115,8 @@ sub catch (&;@) {
 sub finally (&;@) {
   my ( $block, @rest ) = @_;
 
+  croak 'Useless bare finally()' unless defined wantarray;
+
   return (
     bless(\$block, 'Try::Tiny::Finally'),
     @rest,
@@ -120,15 +127,30 @@ sub finally (&;@) {
   package # hide from PAUSE
     Try::Tiny::ScopeGuard;
 
+  use constant UNSTABLE_DOLLARAT => ($] < '5.013002') ? 1 : 0;
+
   sub _new {
     shift;
     bless [ @_ ];
   }
 
   sub DESTROY {
-    my @guts = @{ shift() };
-    my $code = shift @guts;
-    $code->(@guts);
+    my ($code, @args) = @{ $_[0] };
+
+    local $@ if UNSTABLE_DOLLARAT;
+    eval {
+      $code->(@args);
+      1;
+    } or do {
+      warn
+        "Execution of finally() block $code resulted in an exception, which "
+      . '*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. '
+      . 'Your program will continue as if this event never took place. '
+      . "Original exception text follows:\n\n"
+      . (defined $@ ? $@ : '$@ left undefined...')
+      . "\n"
+      ;
+    }
   }
 }
 
@@ -235,7 +257,7 @@ still be invoked.
 
 Once all execution is finished then the C<finally> block, if given, will execute.
 
-=item catch (&;$)
+=item catch (&;@)
 
 Intended to be used in the second argument position of C<try>.
 
@@ -255,7 +277,7 @@ L<Class::Throwable>), you'll need to do:
 
   local $@ = $_;
 
-=item finally (&;$)
+=item finally (&;@)
 
   try     { ... }
   catch   { ... }
@@ -299,6 +321,11 @@ B<You must always do your own error handling in the C<finally> block>. C<Try::Ti
 not do anything about handling possible errors coming from code located in these
 blocks.
 
+Furthermore B<exceptions in C<finally> blocks are not trappable and are unable
+to influence the execution of your program>. This is due to limitation of
+C<DESTROY>-based scope guards, which C<finally> is implemented on top of. This
+may change in a future version of Try::Tiny.
+
 In the same way C<catch()> blesses the code reference this subroutine does the same
 except it bless them as C<Try::Tiny::Finally>.
 
@@ -574,11 +601,11 @@ issues with C<$@>, but you still need to localize to prevent clobbering.
 I gave a lightning talk about this module, you can see the slides (Firefox
 only):
 
-L<http://nothingmuch.woobling.org/talks/takahashi.xul?data=yapc_asia_2009/try_tiny.txt>
+L<web.archive.org/web/20100628040134/http://nothingmuch.woobling.org/talks/takahashi.xul>
 
 Or read the source:
 
-L<http://nothingmuch.woobling.org/talks/yapc_asia_2009/try_tiny.yml>
+L<http://web.archive.org/web/20100305133605/http://nothingmuch.woobling.org/talks/yapc_asia_2009/try_tiny.yml>
 
 =head1 VERSION CONTROL