X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FTry%2FTiny.pm;h=cf8f1f727898b2f39248419adfc4a6ba49e01301;hb=8447a3bf0e1b88bdd07fa584d83c81c64ad192cf;hp=9d46a7629ef1eb40ac5ad59c085d89d73db1166f;hpb=2b0d579d80e5316f00183930df181ea36f80e517;p=p5sagit%2FTry-Tiny.git diff --git a/lib/Try/Tiny.pm b/lib/Try/Tiny.pm index 9d46a76..cf8f1f7 100644 --- a/lib/Try/Tiny.pm +++ b/lib/Try/Tiny.pm @@ -1,17 +1,36 @@ package Try::Tiny; +use 5.006; +# ABSTRACT: minimal try/catch with proper preservation of $@ + +our $VERSION = '0.25'; use strict; use warnings; -our $VERSION = "0.12"; -$VERSION = eval $VERSION if $VERSION =~ /_/; - -use base 'Exporter'; +use Exporter 5.57 'import'; our @EXPORT = our @EXPORT_OK = qw(try catch finally); use Carp; $Carp::Internal{+__PACKAGE__}++; +BEGIN { + my $su = $INC{'Sub/Util.pm'} && defined &Sub::Util::set_subname; + my $sn = $INC{'Sub/Name.pm'} && eval { Sub::Name->VERSION(0.08) }; + unless ($su || $sn) { + $su = eval { require Sub::Util; } && defined &Sub::Util::set_subname; + unless ($su) { + $sn = eval { require Sub::Name; Sub::Name->VERSION(0.08) }; + } + } + + *_subname = $su ? \&Sub::Util::set_subname + : $sn ? \&Sub::Name::subname + : sub { $_[1] }; + *_HAS_SUBNAME = ($su || $sn) ? sub(){1} : sub(){0}; +} + +my @_finally_guards; + # 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 @@ -23,21 +42,26 @@ 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 { - 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' + ); } } @@ -45,6 +69,20 @@ sub try (&;@) { # not perfect, but we could provide a list of additional errors for # $catch->(); + # name the blocks if we have Sub::Name installed + my $caller = caller; + _subname("${caller}::try {...} " => $try) + if _HAS_SUBNAME; + + # set up scope guards to invoke the finally blocks at the end. + # this should really be a function scope lexical variable instead of + # file scope + local but that causes issues with perls < 5.20 due to + # perl rt#119311 + local $_finally_guards[0] = [ + map { Try::Tiny::ScopeGuard->_new($_) } + @finally + ]; + # 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 = $@; @@ -65,20 +103,19 @@ sub try (&;@) { $try->(); }; - return 1; # properly set $fail to false - } and $error = $@; + return 1; # properly set $failed to false + }; - # reset the original value of $@ + # 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 = - map { Try::Tiny::ScopeGuard->_new($_, $failed ? $error : ()) } - @finally; - # at this point $failed contains a true value if the eval died, even if some # destructor overwrote $@ as the eval was unwinding. if ( $failed ) { + # pass $error to the finally blocks + push @$_, $error for @{$_finally_guards[0]}; + # if we got an error, invoke the catch block. if ( $catch ) { # This works like given($error), but is backwards compatible and @@ -101,6 +138,11 @@ sub try (&;@) { sub catch (&;@) { my ( $block, @rest ) = @_; + croak 'Useless bare catch()' unless wantarray; + + my $caller = caller; + _subname("${caller}::catch {...} " => $block) + if _HAS_SUBNAME; return ( bless(\$block, 'Try::Tiny::Catch'), @rest, @@ -110,6 +152,11 @@ sub catch (&;@) { sub finally (&;@) { my ( $block, @rest ) = @_; + croak 'Useless bare finally()' unless wantarray; + + my $caller = caller; + _subname("${caller}::finally {...} " => $block) + if _HAS_SUBNAME; return ( bless(\$block, 'Try::Tiny::Finally'), @rest, @@ -120,15 +167,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" + ; + } } } @@ -138,10 +200,6 @@ __END__ =pod -=head1 NAME - -Try::Tiny - minimal try/catch with proper preservation of $@ - =head1 SYNOPSIS You can use Try::Tiny's C and C to expect and handle exceptional @@ -189,8 +247,8 @@ context or the empty list in list context. The following examples all assign C<"bar"> to C<$x>: my $x = try { die "foo" } catch { "bar" }; - my $x = try { die "foo" } || { "bar" }; - my $x = (try { die "foo" }) // { "bar" }; + my $x = try { die "foo" } || "bar"; + my $x = (try { die "foo" }) // "bar"; my $x = eval { die "foo" } || "bar"; @@ -204,6 +262,13 @@ C blocks are always executed making them suitable for cleanup code which cannot be handled using local. You can add as many C blocks to a given C block as you like. +Note that adding a C block without a preceding C block +suppresses any errors. This behaviour is consistent with using a standalone +C, but it is not consistent with C/C patterns found in +other programming languages, such as Java, Python, Javascript or C#. If you +learnt the C/C pattern from one of these languages, watch out for +this. + =head1 EXPORTS All functions are exported by default using L. @@ -235,7 +300,7 @@ still be invoked. Once all execution is finished then the C block, if given, will execute. -=item catch (&;$) +=item catch (&;@) Intended to be used in the second argument position of C. @@ -255,7 +320,7 @@ L), you'll need to do: local $@ = $_; -=item finally (&;$) +=item finally (&;@) try { ... } catch { ... } @@ -299,6 +364,11 @@ B block>. C blocks are not trappable and are unable +to influence the execution of your program>. This is due to limitation of +C-based scope guards, which C is implemented on top of. This +may change in a future version of Try::Tiny. + In the same way C blesses the code reference this subroutine does the same except it bless them as C. @@ -403,6 +473,8 @@ value. Using Perl 5.10 you can use L. +=for stopwords topicalizer + The C block is invoked in a topicalizer context (like a C block), but note that you can't return a useful value from C using the C blocks without an explicit C. @@ -424,7 +496,7 @@ concisely match errors: =item * C<@_> is not available within the C block, so you need to copy your -arglist. In case you want to work with argument values directly via C<@_> +argument list. In case you want to work with argument values directly via C<@_> aliasing (i.e. allow C<$_[1] = "foo">), you need to pass C<@_> by reference: sub foo { @@ -462,7 +534,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!"; @@ -478,11 +564,13 @@ C introduces another caller stack frame. L is not used. L is used. This lack of magic is considered a feature. +=for stopwords unhygienically + =item * The value of C<$_> in the C block is not guaranteed to be the value of the exception thrown (C<$@>) in the C block. There is no safe way to -ensure this, since C may be used unhygenically in destructors. The only +ensure this, since C may be used unhygienically in destructors. The only guarantee is that the C will be called if an exception is thrown. =item * @@ -530,7 +618,7 @@ confusing behavior: Note that this behavior was changed once again in L. -However, since the entirety of lexical C<$_> is now L is now L, it is unclear whether the new version 18 behavior is final. @@ -574,25 +662,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 +L Or read the source: -L +L =head1 VERSION CONTROL -L - -=head1 AUTHOR - -Yuval Kogman Enothingmuch@woobling.orgE - -=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 =cut