File Coverage

blib/lib/Return/Type.pm
Criterion Covered Total %
statement 75 81 92.5
branch 13 20 65.0
condition 7 14 50.0
subroutine 14 14 100.0
pod 1 2 50.0
total 110 131 83.9


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