Typo in comment
[p5sagit/Try-Tiny.git] / lib / Try / Tiny.pm
index 6f892c6..73c2b16 100644 (file)
@@ -1,23 +1,18 @@
 package Try::Tiny;
+use 5.006;
+# ABSTRACT: minimal try/catch with proper preservation of $@
 
 use strict;
-#use warnings;
+use warnings;
 
-use vars qw(@EXPORT @EXPORT_OK $VERSION @ISA);
-
-BEGIN {
-  require Exporter;
-  @ISA = qw(Exporter);
-}
-
-$VERSION = "0.12";
-
-$VERSION = eval $VERSION;
-
-@EXPORT = @EXPORT_OK = qw(try catch finally);
+use Exporter 5.57 'import';
+our @EXPORT = our @EXPORT_OK = qw(try catch finally);
 
+use Carp;
 $Carp::Internal{+__PACKAGE__}++;
 
+BEGIN { eval "use Sub::Name; 1" or *{subname} = sub {1} }
+
 # Need to prototype as @ not $$ because of the way Perl evaluates the prototype.
 # Keeping it at $$ means you only ever get 1 sub because we need to eval in a list
 # context & not a scalar one
@@ -29,60 +24,65 @@ sub try (&;@) {
   # to $failed
   my $wantarray = wantarray;
 
-  my ( $catch, @finally );
+  # work around perl bug by explicitly initializing these, due to the likelyhood
+  # this will be used in global destruction (perl rt#119311)
+  my ( $catch, @finally ) = ();
 
   # 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 {
-      use Carp;
-      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'
+      );
     }
   }
 
-  # save the value of $@ so we can set $@ back to it in the beginning of the eval
-  my $prev_error = $@;
-
-  my ( @ret, $error, $failed );
-
   # FIXME consider using local $SIG{__DIE__} to accumulate all errors. It's
   # not perfect, but we could provide a list of additional errors for
   # $catch->();
 
-  {
-    # localize $@ to prevent clobbering of previous value by a successful
-    # eval.
-    local $@;
+  # name the blocks if we have Sub::Name installed
+  my $caller = caller;
+  subname("${caller}::try {...} " => $try);
+  subname("${caller}::catch {...} " => $catch) if $catch;
+  subname("${caller}::finally {...} " => $_) foreach @finally;
 
-    # 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 ) {
-        @ret = $try->();
-      } elsif ( defined $wantarray ) {
-        $ret[0] = $try->();
-      } else {
-        $try->();
-      };
-
-      return 1; # properly set $fail to false
+  # save the value of $@ so we can set $@ back to it in the beginning of the eval
+  # and restore $@ after the eval finishes
+  my $prev_error = $@;
+
+  my ( @ret, $error );
+
+  # failed will be true if the eval dies, because 1 will not be returned
+  # from the eval body
+  my $failed = not eval {
+    $@ = $prev_error;
+
+    # evaluate the try block in the correct context
+    if ( $wantarray ) {
+      @ret = $try->();
+    } elsif ( defined $wantarray ) {
+      $ret[0] = $try->();
+    } else {
+      $try->();
     };
 
-    # copy $@ to $error; when we leave this scope, local $@ will revert $@
-    # back to its previous value
-    $error = $@;
-  }
+    return 1; # properly set $failed to false
+  };
+
+  # preserve the current error and reset the original value of $@
+  $error = $@;
+  $@ = $prev_error;
 
   # set up a scope guard to invoke the finally block at the end
   my @guards =
@@ -114,6 +114,8 @@ sub try (&;@) {
 sub catch (&;@) {
   my ( $block, @rest ) = @_;
 
+  croak 'Useless bare catch()' unless wantarray;
+
   return (
     bless(\$block, 'Try::Tiny::Catch'),
     @rest,
@@ -123,6 +125,8 @@ sub catch (&;@) {
 sub finally (&;@) {
   my ( $block, @rest ) = @_;
 
+  croak 'Useless bare finally()' unless wantarray;
+
   return (
     bless(\$block, 'Try::Tiny::Finally'),
     @rest,
@@ -133,15 +137,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"
+      ;
+    }
   }
 }
 
@@ -151,10 +170,6 @@ __END__
 
 =pod
 
-=head1 NAME
-
-Try::Tiny - minimal try/catch with proper localization of $@
-
 =head1 SYNOPSIS
 
 You can use Try::Tiny's C<try> and C<catch> to expect and handle exceptional
@@ -217,6 +232,13 @@ C<finally> blocks are always executed making them suitable for cleanup code
 which cannot be handled using local.  You can add as many C<finally> blocks to a
 given C<try> block as you like.
 
+Note that adding a C<finally> block without a preceding C<catch> block
+suppresses any errors. This behaviour is consistent with using a standalone
+C<eval>, but it is not consistent with C<try>/C<finally> patterns found in
+other programming languages, such as Java, Python, Javascript or C#. If you
+learnt the C<try>/C<finally> pattern from one of these languages, watch out for
+this.
+
 =head1 EXPORTS
 
 All functions are exported by default using L<Exporter>.
@@ -248,7 +270,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>.
 
@@ -268,7 +290,7 @@ L<Class::Throwable>), you'll need to do:
 
   local $@ = $_;
 
-=item finally (&;$)
+=item finally (&;@)
 
   try     { ... }
   catch   { ... }
@@ -312,6 +334,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>.
 
@@ -336,8 +363,9 @@ More specifically, C<$@> is clobbered at the beginning 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 beginning of the C<eval> block.
+For this reason C<try> will actually set C<$@> to its previous value (the one
+available before entering the C<try> block) in the beginning of the C<eval>
+block.
 
 =head2 Localizing $@ silently masks errors
 
@@ -474,7 +502,21 @@ Instead, you should capture the return value:
     my $success = try {
       die;
       1;
+    };
+    return unless $success;
+
+    say "This text WILL NEVER appear!";
+  }
+  # OR
+  sub parent_sub_with_catch {
+    my $success = try {
+      die;
+      1;
     }
+    catch {
+      # do something with $_
+      return undef; #see note
+    };
     return unless $success;
 
     say "This text WILL NEVER appear!";
@@ -540,6 +582,12 @@ confusing behavior:
     }
   }
 
+Note that this behavior was changed once again in L<Perl5 version 18
+|https://metacpan.org/module/perldelta#given-now-aliases-the-global-_>.
+However, since the entirety of lexical C<$_> is now L<considired experimental
+|https://metacpan.org/module/perldelta#Lexical-_-is-now-experimental>, it
+is unclear whether the new version 18 behavior is final.
+
 =back
 
 =head1 SEE ALSO
@@ -580,25 +628,15 @@ 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<http://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
 
-L<http://github.com/nothingmuch/try-tiny/>
-
-=head1 AUTHOR
-
-Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
-
-=head1 COPYRIGHT
-
-  Copyright (c) 2009 Yuval Kogman. All rights reserved.
-  This program is free software; you can redistribute
-  it and/or modify it under the terms of the MIT license.
+L<http://github.com/doy/try-tiny/>
 
 =cut