File Coverage

blib/lib/Function/Return.pm
Criterion Covered Total %
statement 160 197 81.2
branch 62 116 53.4
condition 4 4 100.0
subroutine 30 31 96.7
pod 3 3 100.0
total 259 351 73.7


line stmt bran cond sub pod time code
1             package Function::Return;
2              
3 14     14   1404232 use v5.14.0;
  14         178  
4 14     14   80 use warnings;
  14         35  
  14         580  
5              
6             our $VERSION = "0.15";
7              
8 14     14   7278 use Attribute::Handlers;
  14         62245  
  14         77  
9 14     14   6856 use B::Hooks::EndOfScope;
  14         116010  
  14         98  
10              
11 14     14   8172 use Scope::Upper ();
  14         11448  
  14         323  
12 14     14   7030 use Sub::Meta;
  14         218697  
  14         478  
13 14     14   5916 use Sub::Meta::Library;
  14         1804821  
  14         671  
14 14     14   6573 use Sub::Meta::Finder::FunctionParameters;
  14         63392  
  14         534  
15 14     14   6565 use namespace::autoclean;
  14         93227  
  14         64  
16              
17             my @RETURN_ARGS;
18             my %NO_CHECK;
19              
20             sub import {
21 20     20   1197 my $class = shift;
22 20         63 my %args = @_;
23              
24 20 100       100 my $pkg = $args{pkg} ? $args{pkg} : scalar caller;
25 20 100       86 $NO_CHECK{$pkg} = !!$args{no_check} if exists $args{no_check};
26              
27             {
28             # allow importing package to use attribute
29 14     14   1664 no strict qw(refs);
  14         37  
  14         3935  
  20         38  
30 20         59 my $MODIFY_CODE_ATTRIBUTES = \&Attribute::Handlers::UNIVERSAL::MODIFY_CODE_ATTRIBUTES;
31 20         43 *{"${pkg}::MODIFY_CODE_ATTRIBUTES"} = $MODIFY_CODE_ATTRIBUTES;
  20         120  
32 20         103 *{"${pkg}::_ATTR_CODE_Return"} = $class->can('Return');
  20         85  
33             }
34              
35             #
36             # How to install meta information
37             # 1. At the BEGIN phase, write down the meta information via the `Return` attribute.
38             # 2. At the compile phase, install the meta information in bulk via this `import` subroutine.
39             #
40             # In short,
41             # once Function::Return#import is compiled, the meta-information can be retrieved.
42             #
43             # The Reason Why?
44             #
45             # First NG CASE:
46             # At the **CHECK** phase, write down the meta information via the Return attribute. (Attribute::Handler's default case)
47             # Then, cannot support lazy load.
48             # Ref: case_lazy_load.t
49             #
50             # Second NG CASE:
51             # At the compile phase, install the meta information in **each** via this **Return** attribute.
52             # Then, unable to retrieve meta information for Function::Return from places that are compiled before the Return attribute.
53             # Ref: case_load_and_get_meta.t
54             #
55             on_scope_end {
56 20     20   12579 while (my $args = shift @RETURN_ARGS) {
57 36         122 my ($pkg, $sub, $types) = @$args;
58 36 100 100     268 my $no_check = exists $NO_CHECK{$pkg} ? $NO_CHECK{$pkg} : ($ENV{FUNCTION_RETURN_NO_CHECK}//0);
59              
60 36 100       207 if ($no_check) {
61 9         31 $class->_register_submeta($pkg, $sub, $types);
62             }
63             else {
64 27         121 $class->_register_submeta_and_install($pkg, $sub, $types);
65             }
66             }
67 20         142 };
68              
69 20         1266 return;
70             }
71              
72             sub Return :ATTR(CODE,BEGIN) {
73 36     36 1 117690 my $class = __PACKAGE__;
74 36         116 my ($pkg, undef, $sub, undef, $types) = @_;
75 36   100     178 $types //= [];
76              
77 36         106 push @RETURN_ARGS => [$pkg, $sub, $types];
78 36         93 return;
79 14     14   121 }
  14         47  
  14         125  
80              
81             sub meta {
82 14     14 1 65368 my ($sub) = @_;
83 14         88 Sub::Meta::Library->get($sub);
84             }
85              
86             sub wrap_sub {
87 44     44 1 53147 my ($class, $sub, $types) = @_;
88              
89 44         154 my $meta = Sub::Meta->new(sub => $sub);
90 44         9975 my $shortname = $meta->subname;
91              
92             { # check type
93 44         418 my $file = $meta->file;
  44         109  
94 44         262 my $line = $meta->line;
95 44         310 for my $type (@$types) {
96 46         177 for (qw/check get_message/) {
97 91 100       653 die "Invalid type: $type. require `$_` method at $file line $line.\n"
98             unless $type->can($_)
99             }
100             }
101             }
102              
103 43         316 my @src;
104 43 100       173 push @src => sprintf('_croak "Required list context in fun %s because of multiple return values function" if !wantarray;', $shortname) if @$types > 1;
105              
106             # force LIST context.
107 43         110 push @src => 'my @ret = &Scope::Upper::uplevel($sub, @_, &Scope::Upper::CALLER(0));';
108              
109             # return Empty List
110 43 100       144 push @src => 'return if !@ret;' if @$types == 0;
111              
112             # check count
113 43 100       262 push @src => sprintf(q|_croak qq!Too few return values for fun %s (expected %s, got @{[map { defined $_ ? $_ : 'undef' } @ret]})! if @ret < %d;|,
114             $shortname, "@$types", scalar @$types) if @$types > 0;
115              
116 43         637 push @src => sprintf(q|_croak qq!Too many return values for fun %s (expected %s, got @{[map { defined $_ ? $_ : 'undef' } @ret]})! if @ret > %d;|,
117             $shortname, "@$types", scalar @$types);
118              
119             # type check
120 43         416 for my $i (0 .. $#$types) {
121 45         216 push @src => sprintf(q|_croak qq!Invalid return in fun %s: return %d: @{[$types->[%d]->get_message($ret[%d])]}! unless $types->[%d]->check($ret[%d]);|, $shortname, $i, $i, $i, $i,$i)
122             }
123              
124 43 100       145 push @src => 'return @ret;' if @$types > 1;
125 43 100       124 push @src => 'return $ret[0];' if @$types == 1;
126              
127 43         161 my $src = join "\n", @src;
128 43 100   6   10713 my $code = eval "sub { $src }"; ## no critic
  6 100   5   4576  
  6 50   1   205  
  1 50   1   6  
  1 100   1   2  
  4 0   0   36  
  1 100   3   598  
  1 0   3   40  
  4 50   3   39  
  2 100       67  
  1 50       80  
  4 0       1038  
  4 50       130  
  1 0       7  
  0 50       0  
  3 50       12  
  1 0       928  
  1 50       30  
  4 0       30  
  2 50       42  
  3 50       158  
  1 0       810  
  1 0       32  
  0 0       0  
  0 100       0  
  1 0       5  
  0 50       0  
  0 0       0  
  1 50       7  
  0 50       0  
  1 50       78  
  1 100       516  
  1 50       27  
  0 50       0  
  0 0       0  
  1 0       4  
  0 0       0  
  0 0       0  
  1 100       6  
  1 0       50  
  0 50       0  
  0 100       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0         0  
  3         3359  
  1         11  
  1         40  
  0         0  
  0         0  
  1         5  
  0         0  
  0         0  
  1         8  
  0         0  
  1         30  
  0         0  
  1         30  
  3         2507  
  1         9  
  1         27  
  1         3  
  1         12  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  3         2472  
  1         10  
  1         28  
  0         0  
  0         0  
  1         5  
  1         3  
  3         19  
  0            
  0            
  0            
  0            
  0            
129 43         558 return $code;
130             }
131              
132             sub _croak {
133 28     28   8696 my (undef, $file, $line) = caller 1;
134 28         513 die @_, " at $file line $line.\n"
135             }
136              
137             sub _register_submeta {
138 9     9   23 my ($class, $pkg, $sub, $types) = @_;
139              
140 9         32 my $meta = Sub::Meta->new(sub => $sub, stashname => $pkg);
141 9         2047 $meta->set_returns(_normalize_types($types));
142              
143 9 100       249 if (my $materials = Sub::Meta::Finder::FunctionParameters::find_materials($sub)) {
144 2         523 $meta->set_is_method($materials->{is_method});
145 2         13 $meta->set_parameters($materials->{parameters});
146             }
147              
148 9         371 Sub::Meta::Library->register($sub, $meta);
149 9         317 return;
150             }
151              
152             sub _register_submeta_and_install {
153 27     27   71 my ($class, $pkg, $sub, $types) = @_;
154              
155 27         281 my $original_meta = Sub::Meta->new(sub => $sub);
156 27         9109 my $wrapped = $class->wrap_sub($sub, $types);
157              
158 27         136 my $meta = Sub::Meta->new(sub => $wrapped, stashname => $pkg);
159 27         6470 $meta->set_returns(_normalize_types($types));
160              
161 27 100       817 if (my $materials = Sub::Meta::Finder::FunctionParameters::find_materials($sub)) {
162 4         10350 $meta->set_is_method($materials->{is_method});
163 4         29 $meta->set_parameters($materials->{parameters});
164             }
165              
166 27         974 $meta->apply_meta($original_meta);
167 27         5595 Sub::Meta::Library->register($wrapped, $meta);
168              
169             {
170 14     14   14951 no strict qw(refs);
  14         48  
  14         575  
  27         638767  
171 14     14   93 no warnings qw(redefine);
  14         64  
  14         2297  
172 27         49 *{$meta->fullname} = $wrapped;
  27         107  
173             }
174 27         1324 return;
175             }
176              
177             sub _normalize_types {
178 36     36   84 my $types = shift;
179 36 100       105 if (@$types == 1) {
180 21         87 return $types->[0];
181             }
182             else {
183 15         55 return $types;
184             }
185             }
186              
187             1;
188             __END__