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   1519137 use v5.14.0;
  14         174  
4 14     14   126 use warnings;
  14         29  
  14         738  
5              
6             our $VERSION = "0.14";
7              
8 14     14   8265 use Attribute::Handlers;
  14         68682  
  14         87  
9 14     14   8440 use B::Hooks::EndOfScope;
  14         127942  
  14         111  
10              
11 14     14   9207 use Scope::Upper ();
  14         12847  
  14         342  
12 14     14   8332 use Sub::Meta;
  14         238346  
  14         575  
13 14     14   8310 use Sub::Meta::Library;
  14         12690  
  14         564  
14 14     14   6973 use Sub::Meta::Finder::FunctionParameters;
  14         66715  
  14         603  
15 14     14   7943 use namespace::autoclean;
  14         98729  
  14         67  
16              
17             my @RETURN_ARGS;
18             my %NO_CHECK;
19              
20             sub import {
21 20     20   1378 my $class = shift;
22 20         66 my %args = @_;
23              
24 20 100       104 my $pkg = $args{pkg} ? $args{pkg} : scalar caller;
25 20 100       81 $NO_CHECK{$pkg} = !!$args{no_check} if exists $args{no_check};
26              
27             {
28             # allow importing package to use attribute
29 14     14   1784 no strict qw(refs);
  14         43  
  14         4111  
  20         35  
30 20         48 my $MODIFY_CODE_ATTRIBUTES = \&Attribute::Handlers::UNIVERSAL::MODIFY_CODE_ATTRIBUTES;
31 20         39 *{"${pkg}::MODIFY_CODE_ATTRIBUTES"} = $MODIFY_CODE_ATTRIBUTES;
  20         129  
32 20         174 *{"${pkg}::_ATTR_CODE_Return"} = $class->can('Return');
  20         88  
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   90036 while (my $args = shift @RETURN_ARGS) {
57 36         129 my ($pkg, $sub, $types) = @$args;
58 36 100 100     298 my $no_check = exists $NO_CHECK{$pkg} ? $NO_CHECK{$pkg} : ($ENV{FUNCTION_RETURN_NO_CHECK}//0);
59              
60 36 100       147 if ($no_check) {
61 9         28 $class->_register_submeta($pkg, $sub, $types);
62             }
63             else {
64 27         121 $class->_register_submeta_and_install($pkg, $sub, $types);
65             }
66             }
67 20         146 };
68              
69 20         1265 return;
70             }
71              
72             sub Return :ATTR(CODE,BEGIN) {
73 36     36 1 654298 my $class = __PACKAGE__;
74 36         129 my ($pkg, undef, $sub, undef, $types) = @_;
75 36   100     189 $types //= [];
76              
77 36         121 push @RETURN_ARGS => [$pkg, $sub, $types];
78 36         98 return;
79 14     14   127 }
  14         42  
  14         163  
80              
81             sub meta {
82 14     14 1 73476 my ($sub) = @_;
83 14         143 Sub::Meta::Library->get($sub);
84             }
85              
86             sub wrap_sub {
87 42     42 1 19765 my ($class, $sub, $types) = @_;
88              
89 42         146 my $meta = Sub::Meta->new(sub => $sub);
90 42         5859 my $shortname = $meta->subname;
91              
92             { # check type
93 42         352 my $file = $meta->file;
  42         111  
94 42         223 my $line = $meta->line;
95 42         250 for my $type (@$types) {
96 44         178 for (qw/check get_message/) {
97 87 100       642 die "Invalid type: $type. require `$_` method at $file line $line.\n"
98             unless $type->can($_)
99             }
100             }
101             }
102              
103 41         285 my @src;
104 41 100       218 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 41         91 push @src => 'my @ret = &Scope::Upper::uplevel($sub, @_, &Scope::Upper::CALLER(0));';
108              
109             # return Empty List
110 41 100       122 push @src => 'return if !@ret;' if @$types == 0;
111              
112             # check count
113 41 100       245 push @src => sprintf(q|_croak "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 41         653 push @src => sprintf(q|_croak "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 41         412 for my $i (0 .. $#$types) {
121 43         208 push @src => sprintf(q|_croak "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 41 100       125 push @src => 'return @ret;' if @$types > 1;
125 41 100       145 push @src => 'return $ret[0];' if @$types == 1;
126              
127 41         164 my $src = join "\n", @src;
128 41 100   6   10633 my $code = eval "sub { $src }"; ## no critic
  6 100   5   4511  
  6 50   1   179  
  1 50   1   5  
  1 100   1   2  
  4 0   0   24  
  1 100   3   654  
  1 0   3   27  
  4 50   3   62  
  2 100       71  
  1 50       72  
  4 0       872  
  4 50       165  
  1 0       9  
  0 50       0  
  3 50       12  
  1 0       948  
  1 50       26  
  4 0       25  
  2 50       42  
  3 50       154  
  1 0       946  
  1 0       29  
  0 0       0  
  0 100       0  
  1 0       5  
  0 50       0  
  0 0       0  
  1 50       12  
  0 50       0  
  1 50       106  
  1 100       583  
  1 50       28  
  0 50       0  
  0 0       0  
  1 0       5  
  0 0       0  
  0 0       0  
  1 100       7  
  1 0       48  
  0 50       0  
  0 100       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0         0  
  3         3794  
  1         63  
  1         58  
  0         0  
  0         0  
  1         5  
  0         0  
  0         0  
  1         10  
  0         0  
  1         43  
  0         0  
  1         31  
  3         2592  
  1         13  
  1         27  
  1         4  
  1         12  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  3         2547  
  1         14  
  1         28  
  0         0  
  0         0  
  1         5  
  1         3  
  3         34  
  0            
  0            
  0            
  0            
  0            
129 41         356 return $code;
130             }
131              
132             sub _croak {
133 27     27   8907 my (undef, $file, $line) = caller 1;
134 27         490 die @_, " at $file line $line.\n"
135             }
136              
137             sub _register_submeta {
138 9     9   23 my ($class, $pkg, $sub, $types) = @_;
139              
140 9         29 my $meta = Sub::Meta->new(sub => $sub, stashname => $pkg);
141 9         1371 $meta->set_returns(_normalize_types($types));
142              
143 9 100       277 if (my $materials = Sub::Meta::Finder::FunctionParameters::find_materials($sub)) {
144 2         542 $meta->set_is_method($materials->{is_method});
145 2         15 $meta->set_parameters($materials->{parameters});
146             }
147              
148 9         340 Sub::Meta::Library->register($sub, $meta);
149 9         216 return;
150             }
151              
152             sub _register_submeta_and_install {
153 27     27   80 my ($class, $pkg, $sub, $types) = @_;
154              
155 27         242 my $original_meta = Sub::Meta->new(sub => $sub);
156 27         5211 my $wrapped = $class->wrap_sub($sub, $types);
157              
158 27         137 my $meta = Sub::Meta->new(sub => $wrapped, stashname => $pkg);
159 27         4304 $meta->set_returns(_normalize_types($types));
160              
161 27 100       1050 if (my $materials = Sub::Meta::Finder::FunctionParameters::find_materials($sub)) {
162 4         11915 $meta->set_is_method($materials->{is_method});
163 4         46 $meta->set_parameters($materials->{parameters});
164             }
165              
166 27         1173 $meta->apply_meta($original_meta);
167 27         5590 Sub::Meta::Library->register($wrapped, $meta);
168              
169             {
170 14     14   15928 no strict qw(refs);
  14         56  
  14         694  
  27         816  
171 14     14   109 no warnings qw(redefine);
  14         49  
  14         2536  
172 27         48 *{$meta->fullname} = $wrapped;
  27         72  
173             }
174 27         1152 return;
175             }
176              
177             sub _normalize_types {
178 36     36   71 my $types = shift;
179 36 100       121 if (@$types == 1) {
180 21         95 return $types->[0];
181             }
182             else {
183 15         64 return $types;
184             }
185             }
186              
187             1;
188             __END__