File Coverage

blib/lib/Return/Type.pm
Criterion Covered Total %
statement 80 85 94.1
branch 19 24 79.1
condition 13 22 59.0
subroutine 14 14 100.0
pod 1 2 50.0
total 127 147 86.3


line stmt bran cond sub pod time code
1 4     4   209205 use 5.008;
  4         34  
2 4     4   21 use strict;
  4         8  
  4         78  
3 4     4   19 use warnings;
  4         8  
  4         240  
4              
5             package Return::Type;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.007';
9              
10 4     4   2207 use Attribute::Handlers;
  4         16229  
  4         22  
11 4     4   1093 use Eval::TypeTiny qw( eval_closure );
  4         2519  
  4         27  
12 4     4   8491 use Sub::Util qw( subname set_subname );
  4         10  
  4         277  
13 4     4   1151 use Types::Standard qw( Any ArrayRef HashRef Int );
  4         141912  
  4         41  
14 4     4   4222 use Types::TypeTiny qw( to_TypeTiny );
  4         11  
  4         27  
15              
16             sub _inline_type_check
17             {
18 20     20   52 my $class = shift;
19 20         64 my ($type, $var, $env, $suffix) = @_;
20            
21 20 50       62 return $type->inline_assert($var) if $type->can_be_inlined;
22            
23 0         0 $env->{'$type_'.$suffix} = \$type;
24 0         0 return sprintf('$type_%s->assert_return(%s);', $suffix, $var);
25             }
26              
27             sub _inline_type_coerce_and_check
28             {
29 8     8   16 my $class = shift;
30 8         22 my ($type, $var, $env, $suffix) = @_;
31            
32 8         15 my $coerce = '';
33 8 100 66     33 if ($type->has_coercion and $type->coercion->can_be_inlined)
    50          
34             {
35 4         489 $coerce = sprintf('%s = %s;', $var, $type->coercion->inline_coercion($var));
36             }
37             elsif ($type->has_coercion)
38             {
39 4         391 $env->{'$coercion_'.$suffix} = \( $type->coercion );
40 4         38 $coerce = sprintf('%s = $coercion_%s->coerce(%s);', $var, $suffix, $var);
41             }
42            
43 8         2166 return $coerce . $class->_inline_type_check(@_);
44             }
45              
46             sub wrap_sub
47             {
48 10     10 1 80996 my $class = shift;
49 10         22 my $sub = $_[0];
50 10         57 local %_ = @_[ 1 .. $#_ ];
51            
52 10   66     108 $_{$_} &&= to_TypeTiny($_{$_}) for qw( list scalar );
53 10   33     300 $_{scalar} ||= Any;
54 10 100 66     96 $_{list} ||= ($_{scalar} == Any ? Any : ArrayRef[$_{scalar}]);
55            
56 10         18723 my $prototype = prototype($sub);
57 10 50       43 $prototype = defined($prototype) ? "($prototype)" : "";
58            
59 10         35 my %env = ( '$sub' => \$sub );
60 10         70 my @src = sprintf('sub %s { my $wa = wantarray;', $prototype);
61 10         21 my $call = '$sub->(@_)';
62            
63 10 50       37 if ($_{scope_upper})
64             {
65 0         0 require Scope::Upper;
66 0         0 $call = '&Scope::Upper::uplevel($sub => (@_) => &Scope::Upper::SUB(&Scope::Upper::SUB))';
67             }
68            
69 10   100     71 exists($_{$_}) || ($_{$_} = $_{coerce}) for qw( coerce_list coerce_scalar );
70 10 100       133 my $inline_list = $_{coerce_list} ? '_inline_type_coerce_and_check' : '_inline_type_check';
71 10 100       27 my $inline_scalar = $_{coerce_scalar} ? '_inline_type_coerce_and_check' : '_inline_type_check';
72            
73             # List context
74 10         21 push @src, 'if ($wa) {';
75 10 100       42 if ( $_{list}->is_a_type_of(HashRef) )
76             {
77 1         101 push @src, 'my $rv = do { use warnings FATAL => qw(misc); +{' . $call . '} };';
78 1         8 push @src, $class->$inline_list($_{list}, '$rv', \%env, 'l');
79 1         99 push @src, 'return %$rv;';
80             }
81             else
82             {
83 9         7928 push @src, 'my $rv = [' . $call . '];';
84 9         59 push @src, $class->$inline_list($_{list}, '$rv', \%env, 'l');
85 9         1217 push @src, 'return @$rv;';
86             }
87 10         24 push @src, '}';
88            
89             # Scalar context
90 10         24 push @src, 'elsif (defined $wa) {';
91 10         23 push @src, 'my $rv = ' . $call . ';';
92 10         44 push @src, $class->$inline_scalar($_{scalar}, '$rv', \%env, 's');
93 10         939 push @src, 'return $rv;';
94 10         25 push @src, '}';
95            
96             # Void context - cannot request a value to check, so check must be skipped
97 10         24 push @src, "$call;";
98            
99 10         19 push @src, '}';
100            
101 10         39 my $rv = eval_closure(
102             source => \@src,
103             environment => \%env,
104             );
105 10         6446 return set_subname(subname($sub), $rv);
106             }
107              
108             my $Void;
109              
110             sub UNIVERSAL::ReturnType :ATTR(CODE)
111             {
112 2     2 0 11727 my ($package, $symbol, $referent, $attr, $data) = @_;
113            
114 2 50 50     16 if ( (ref($data)||'') ne 'ARRAY' ) {
115 0         0 $data = [ $data ];
116             }
117              
118 2 100 66     13 if (@$data == 1 and $data->[0] eq 'Void') {
119 1   33     5 $Void ||= Any->complementary_type->create_child_type(name => 'Void');
120 1         199 $data = [ scalar => $Void, list => $Void ];
121             }
122            
123 4     4   3561 no warnings qw(redefine);
  4         8  
  4         388  
124 2 100       13 my %args = (@$data % 2) ? (scalar => @$data) : @$data;
125 2         10 *$symbol = __PACKAGE__->wrap_sub($referent, %args);
126 4     4   29 }
  4         10  
  4         24  
127              
128             1;
129              
130             __END__
131              
132             =pod
133              
134             =encoding utf-8
135              
136             =head1 NAME
137              
138             Return::Type - specify a return type for a function (optionally with coercion)
139              
140             =head1 SYNOPSIS
141              
142             use Return::Type;
143             use Types::Standard qw(Int);
144            
145             sub first_item :ReturnType(Int) {
146             return $_[0];
147             }
148            
149             my $answer = first_item(42, 43, 44); # returns 42
150             my $pie = first_item(3.141592); # throws an error!
151              
152             =head1 DESCRIPTION
153              
154             Return::Type allows you to specify a return type for your subs. Type
155             constraints from any L<Type::Tiny>, L<MooseX::Types> or L<MouseX::Types>
156             type library are supported.
157              
158             The simple syntax for specifying a type constraint is shown in the
159             L</SYNOPSIS>. If the attribute is passed a single type constraint as shown,
160             this will be applied to the return value if called in scalar context, and
161             to each item in the returned list if called in list context. (If the sub
162             is called in void context, type constraints are simply ignored.)
163              
164             It is possible to specify different type constraints for scalar and
165             list context:
166              
167             sub foo :ReturnType(scalar => Int, list => HashRef[Num]) {
168             if (wantarray) {
169             return (pie => 3.141592);
170             }
171             else {
172             return 42;
173             }
174             }
175              
176             The return value is not type checked if the function is called in void
177             context.
178              
179             # Note that the ~Any type is the opposite of Any.
180             # So all values will fail the type check.
181             # That means that the following function can only
182             # be called in void context.
183             #
184             sub foo :ReturnType(scalar => ~Any, list => ~Any) {
185             ...;
186             }
187            
188             # Shortcut for the above.
189             #
190             sub foo :ReturnType(Void) {
191             ...;
192             }
193              
194             Note that because type constraint libraries are really aimed at
195             validating scalars, the type constraint for the list is specified as
196             a I<hashref> of numbers and not a hash of numbers! For the purposes
197             of validation against the type constraint, we slurp the returned list
198             into a temporary arrayref or hashref.
199              
200             For type constraints with coercions, you can also pass the option
201             C<< coerce => 1 >>:
202              
203             use Return::Type;
204             use Types::Standard qw( Int Num );
205            
206             # Define a subtype of "Int" at compile time, which can
207             # coerce from "Num" by rounding to nearest integer.
208             use constant Rounded => Int->plus_coercions(Num, sub { int($_) });
209            
210             sub first_item :ReturnType(scalar => Rounded, coerce => 1) {
211             return $_[0];
212             }
213            
214             my $answer = first_item(42, 43, 44); # returns 42
215             my $pie = first_item(3.141592); # returns 3
216              
217             The options C<coerce_scalar> and C<coerce_list> are also available if
218             you wish to enable coercion only in particular contexts.
219              
220             =head2 Power-user Inferface
221              
222             Rather than using the C<< :ReturnType >> attribute, it's possible to
223             wrap a coderef like this:
224              
225             my $wrapped = Return::Type->wrap_sub($orig, %options);
226              
227             The accepted options are C<scalar>, C<list>, C<coerce>, C<coerce_list>,
228             and C<coerce_scalar>, as per the attribute-based interface.
229              
230             There is an additional option C<scope_upper> which will load and use
231             L<Scope::Upper> so that things like C<caller> used within the wrapped
232             sub are unaware of being wrapped. This behaviour was the default
233             prior to Return::Type 0.004, but is now optional and disabled by
234             default.
235              
236             =begin trustme
237              
238             =item wrap_sub
239              
240             =end trustme
241              
242             =head1 BUGS
243              
244             Please report any bugs to
245             L<http://rt.cpan.org/Dist/Display.html?Queue=Return-Type>.
246              
247             =head1 SUPPORT
248              
249             B<< IRC: >> support is available through in the I<< #moops >> channel
250             on L<irc.perl.org|http://www.irc.perl.org/channels.html>.
251              
252             =head1 SEE ALSO
253              
254             L<Attribute::Contract>,
255             L<Sub::Filter>,
256             L<Sub::Contract>.
257              
258             =head1 AUTHOR
259              
260             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
261              
262             =head1 COPYRIGHT AND LICENCE
263              
264             This software is copyright (c) 2013-2014 by Toby Inkster.
265              
266             This is free software; you can redistribute it and/or modify it under
267             the same terms as the Perl 5 programming language system itself.
268              
269             =head1 DISCLAIMER OF WARRANTIES
270              
271             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
272             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
273             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
274