File Coverage

blib/lib/Sub/Meta.pm
Criterion Covered Total %
statement 289 289 100.0
branch 130 130 100.0
condition 50 50 100.0
subroutine 79 79 100.0
pod 57 57 100.0
total 605 605 100.0


line stmt bran cond sub pod time code
1             package Sub::Meta;
2 29     29   4329111 use 5.010;
  29         234  
3 29     29   126 use strict;
  29         45  
  29         456  
4 29     29   105 use warnings;
  29         71  
  29         927  
5              
6             our $VERSION = "0.15";
7              
8 29     29   130 use Carp ();
  29         52  
  29         452  
9 29     29   134 use Scalar::Util ();
  29         47  
  29         439  
10 29     29   9565 use Sub::Identify ();
  29         24075  
  29         489  
11 29     29   9219 use Sub::Util ();
  29         5652  
  29         485  
12 29     29   12194 use attributes ();
  29         28152  
  29         725  
13              
14 29     29   10646 use Sub::Meta::Parameters;
  29         72  
  29         800  
15 29     29   11404 use Sub::Meta::Returns;
  29         76  
  29         1175  
16              
17             BEGIN {
18             # for Pure Perl
19 29     29   1024 $ENV{PERL_SUB_IDENTIFY_PP} = $ENV{PERL_SUB_META_PP}; ## no critic (RequireLocalizedPunctuationVars)
20             }
21              
22             use overload
23 29         233 fallback => 1,
24             eq => \&is_same_interface
25 29     29   193 ;
  29         53  
26              
27 175     175 1 743 sub parameters_class { return 'Sub::Meta::Parameters' }
28 178     178 1 706 sub returns_class { return 'Sub::Meta::Returns' }
29              
30 9     9   88 sub _croak { require Carp; goto &Carp::croak }
  9         826  
31              
32             sub new {
33 200     200 1 268407 my ($class, @args) = @_;
34              
35             my %args = @args == 1 && (ref $args[0]||"") ne "HASH" ? _croak "single arg must be hashref"
36 200 100 100     1139 : @args == 1 ? %{$args[0]}
  91 100       341  
37             : @args;
38              
39 196         409 my $self = bless \%args => $class;
40              
41 196 100       539 $self->set_sub(delete $args{sub}) if exists $args{sub}; # build subinfo
42 196 100       539 $self->set_subname(delete $args{subname}) if exists $args{subname};
43 196 100       430 $self->set_stashname(delete $args{stashname}) if exists $args{stashname};
44 196 100       416 $self->set_fullname(delete $args{fullname}) if exists $args{fullname};
45              
46 196         556 $self->set_is_method($self->_normalize_args_is_method(\%args));
47 196         560 $self->set_parameters($self->_normalize_args_parameters(\%args));
48 196         806 $self->set_returns($args{returns});
49              
50             # cleaning
51 196         387 delete $args{args};
52 196         259 delete $args{slurpy};
53 196         284 delete $args{invocant};
54 196         263 delete $args{nshift};
55              
56 196         497 return $self;
57             }
58              
59             sub _normalize_args_is_method {
60 196     196   374 my ($self, $args) = @_;
61              
62 196 100       894 return !!$args->{invocant} if exists $args->{invocant};
63 192 100       442 return !!$args->{nshift} if exists $args->{nshift};
64 190 100 100     434 return !!$args->{parameters}{nshift} if exists $args->{parameters} && exists $args->{parameters}{nshift};
65 189 100 100     464 return !!$args->{parameters}{invocant} if exists $args->{parameters} && exists $args->{parameters}{invocant};
66 188 100       445 return !!$args->{is_method} if exists $args->{is_method};
67 168         522 return !!0;
68             }
69              
70             sub _normalize_args_parameters {
71 196     196   354 my ($self, $args) = @_;
72              
73 196 100       400 if (exists $args->{parameters}) {
74 27         94 return $args->{parameters};
75             }
76             else {
77             my $nshift = exists $args->{nshift} ? $args->{nshift}
78 169 100       424 : $self->is_method ? 1
    100          
79             : 0;
80              
81 169         246 my $parameters;
82 169 100       389 $parameters->{args} = $args->{args} if exists $args->{args};
83 169 100       334 $parameters->{slurpy} = $args->{slurpy} if exists $args->{slurpy};
84 169 100       318 $parameters->{invocant} = $args->{invocant} if exists $args->{invocant};
85 169         295 $parameters->{nshift} = $nshift;
86 169         517 return $parameters;
87             }
88             }
89              
90 526     526 1 17150 sub sub() :method { my $self = shift; return $self->{sub} } ## no critic (ProhibitBuiltinHomonyms)
  526         1804  
91 267   100 267 1 5126 sub subname() { my $self = shift; return $self->subinfo->[1] // '' }
  267         399  
92 49   100 49 1 4559 sub stashname() { my $self = shift; return $self->subinfo->[0] // '' }
  49         87  
93             sub fullname() {
94 40     40 1 4266 my $self = shift;
95 40         63 my $s = '';
96 40 100       101 $s .= $self->stashname . '::' if $self->has_stashname;
97 40 100       102 $s .= $self->subname if $self->has_subname;
98 40         120 return $s;
99             }
100              
101             sub subinfo() {
102 718     718 1 5611 my $self = shift;
103 718 100       3460 return $self->{subinfo} if $self->{subinfo};
104 93         218 $self->{subinfo} = $self->_build_subinfo;
105 93         297 return $self->{subinfo};
106             }
107              
108 55   100 55 1 7369 sub file() { my $self = shift; return $self->{file} ||= $self->_build_file }
  55         237  
109 55   100 55 1 3687 sub line() { my $self = shift; return $self->{line} ||= $self->_build_line }
  55         214  
110 83   100 83 1 3638 sub prototype() :method { my $self = shift; return $self->{prototype} ||= $self->_build_prototype } ## no critic (ProhibitBuiltinHomonyms)
  83         357  
111 83   100 83 1 3796 sub attribute() { my $self = shift; return $self->{attribute} ||= $self->_build_attribute }
  83         365  
112 58   100 58 1 5593 sub is_constant() { my $self = shift; return $self->{is_constant} ||= !!$self->_build_is_constant }
  58         240  
113 562     562 1 6692 sub is_method() { my $self = shift; return !!$self->{is_method} }
  562         2345  
114 928     928 1 6284 sub parameters() { my $self = shift; return $self->{parameters} }
  928         3799  
115 522     522 1 8588 sub returns() { my $self = shift; return $self->{returns} }
  522         2400  
116              
117 15     15 1 2508 sub args() { my $self = shift; return $self->parameters->args }
  15         32  
118 2     2 1 6 sub all_args() { my $self = shift; return $self->parameters->all_args }
  2         4  
119 12     12 1 37 sub slurpy() { my $self = shift; return $self->parameters->slurpy }
  12         26  
120 11     11 1 23 sub nshift() { my $self = shift; return $self->parameters->nshift }
  11         23  
121 11     11 1 21 sub invocant() { my $self = shift; return $self->parameters->invocant }
  11         22  
122 1     1 1 2 sub invocants() { my $self = shift; return $self->parameters->invocants }
  1         2  
123              
124 120     120 1 4204 sub has_sub() { my $self = shift; return defined $self->{sub} }
  120         379  
125 262     262 1 6193 sub has_subname() { my $self = shift; return defined $self->subinfo->[1] }
  262         449  
126 67     67 1 4107 sub has_stashname() { my $self = shift; return defined $self->subinfo->[0] }
  67         128  
127 27     27 1 4054 sub has_prototype() { my $self = shift; return !!$self->prototype } # after build_prototype
  27         70  
128 27     27 1 4133 sub has_attribute() { my $self = shift; return !!$self->attribute } # after build_attribute
  27         61  
129 27     27 1 4161 sub has_file() { my $self = shift; return defined $self->{file} }
  27         72  
130 27     27 1 4094 sub has_line() { my $self = shift; return defined $self->{line} }
  27         66  
131              
132             sub set_sub {
133 28     28 1 69 my ($self, $v) = @_;
134 28         291 $self->{sub} = $v;
135 28         88 Scalar::Util::weaken($self->{sub});
136              
137             # rebuild
138 28         99 for (qw/subinfo file line prototype attribute is_constant/) {
139 168         944 delete $self->{$_};
140 168         356 $self->$_;
141             }
142 28         61 return $self;
143             }
144              
145 57     57 1 103 sub set_subname { my ($self, $v) = @_; $self->{subinfo}[1] = $v; return $self }
  57         189  
  57         84  
146 2     2 1 9 sub set_stashname { my ($self, $v) = @_; $self->{subinfo}[0] = $v; return $self }
  2         6  
  2         9  
147             sub set_fullname {
148 14     14 1 7125 my ($self, $v) = @_;
149 14 100       110 $self->set_subinfo($v =~ m!^(.+)::([^:]+)$! ? [$1, $2] : []);
150 14         40 return $self;
151             }
152             sub set_subinfo {
153 25     25 1 2314 my ($self, $args) = @_;
154 25         74 $self->{subinfo} = [ $args->[0], $args->[1] ];
155 22         48 return $self;
156             }
157              
158 1     1 1 6 sub set_file { my ($self, $v) = @_; $self->{file} = $v; return $self }
  1         3  
  1         6  
159 1     1 1 5 sub set_line { my ($self, $v) = @_; $self->{line} = $v; return $self }
  1         3  
  1         5  
160 1     1 1 6 sub set_is_constant { my ($self, $v) = @_; $self->{is_constant} = $v; return $self }
  1         3  
  1         7  
161 3     3 1 10 sub set_prototype { my ($self, $v) = @_; $self->{prototype} = $v; return $self }
  3         8  
  3         8  
162 3     3 1 40 sub set_attribute { my ($self, $v) = @_; $self->{attribute} = $v; return $self }
  3         8  
  3         8  
163 198     198 1 377 sub set_is_method { my ($self, $v) = @_; $self->{is_method} = $v; return $self }
  198         449  
  198         270  
164              
165             sub set_parameters {
166 203     203 1 1944 my ($self, @args) = @_;
167 203         294 my $v = $args[0];
168 203 100       557 if (Scalar::Util::blessed($v)) {
169 28 100       124 if ($v->isa('Sub::Meta::Parameters')) {
170 27         57 $self->{parameters} = $v
171             }
172             else {
173 1         3 _croak('object must be Sub::Meta::Parameters');
174             }
175             }
176             else {
177 175         412 $self->{parameters} = $self->parameters_class->new(@args);
178             }
179 200         353 return $self
180             }
181              
182             sub set_args {
183 3     3 1 8 my ($self, $args) = @_;
184 3         5 $self->parameters->set_args($args);
185 3         10 return $self;
186             }
187              
188             sub set_slurpy {
189 3     3 1 9 my ($self, @args) = @_;
190 3         6 $self->parameters->set_slurpy(@args);
191 3         12 return $self;
192             }
193              
194             sub set_nshift {
195 3     3 1 16 my ($self, $v) = @_;
196 3 100 100     6 if ($self->is_method && $v == 0) {
197 1         3 _croak 'nshift of method cannot be zero';
198             }
199 2         5 $self->parameters->set_nshift($v);
200 2         2 return $self;
201             }
202              
203             sub set_invocant {
204 1     1 1 3 my ($self, $v) = @_;
205 1         2 $self->parameters->set_invocant($v);
206 1         3 return $self;
207             }
208              
209             sub set_returns {
210 199     199 1 575 my ($self, @args) = @_;
211 199         302 my $v = $args[0];
212 199 100 100     817 if (Scalar::Util::blessed($v) && $v->isa('Sub::Meta::Returns')) {
213 21         46 $self->{returns} = $v
214             }
215             else {
216 178         527 $self->{returns} = $self->returns_class->new(@args);
217             }
218 199         383 return $self
219             }
220              
221             sub _build_subinfo {
222 93     93   120 my $self = shift;
223 93 100       207 return [] unless $self->has_sub;
224 28         82 my @info = Sub::Identify::get_code_info($self->sub);
225 28 100       118 return [ $info[0], $info[1] eq '__ANON__' ? undef : $info[1] ];
226             }
227              
228 46 100   46   77 sub _build_file { my $self = shift; return $self->sub ? (Sub::Identify::get_code_location($self->sub))[0] : undef }
  46         83  
229 46 100   46   81 sub _build_line { my $self = shift; return $self->sub ? (Sub::Identify::get_code_location($self->sub))[1] : undef }
  46         100  
230 55 100   55   85 sub _build_is_constant { my $self = shift; return $self->sub ? Sub::Identify::is_sub_constant($self->sub) : undef }
  55         139  
231 72 100   72   105 sub _build_prototype { my $self = shift; return $self->sub ? Sub::Util::prototype($self->sub) : undef }
  72         173  
232 66 100   66   107 sub _build_attribute { my $self = shift; return $self->sub ? [ attributes::get($self->sub) ] : undef }
  66         155  
233              
234             sub apply_subname {
235 3     3 1 11 my ($self, $subname) = @_;
236 3 100       7 _croak 'apply_subname requires subroutine reference' unless $self->sub;
237 2         5 $self->set_subname($subname);
238 2         5 Sub::Util::set_subname($self->fullname, $self->sub);
239 2         8 return $self;
240             }
241              
242             sub apply_prototype {
243 3     3 1 12 my ($self, $prototype) = @_;
244 3 100       5 _croak 'apply_prototype requires subroutine reference' unless $self->sub;
245 2         5 Sub::Util::set_prototype($prototype, $self->sub);
246 2         7 $self->set_prototype($prototype);
247 2         5 return $self;
248             }
249              
250             sub apply_attribute {
251 4     4 1 30 my ($self, @attribute) = @_;
252 4 100       10 _croak 'apply_attribute requires subroutine reference' unless $self->sub;
253             {
254 29     29   61652 no warnings qw(misc); ## no critic (ProhibitNoWarnings)
  29         63  
  29         24957  
  3         4  
255 3         5 attributes->import($self->stashname, $self->sub, @attribute);
256             }
257 2         177 $self->set_attribute($self->_build_attribute);
258 2         6 return $self;
259             }
260              
261             sub apply_meta {
262 1     1 1 7 my ($self, $other) = @_;
263              
264 1         3 $self->apply_subname($other->subname);
265 1         3 $self->apply_prototype($other->prototype);
266 1         2 $self->apply_attribute(@{$other->attribute});
  1         2  
267              
268 1         4 return $self;
269             }
270              
271             sub is_same_interface {
272 39     39 1 77 my ($self, $other) = @_;
273              
274 39 100 100     287 return unless Scalar::Util::blessed($other) && $other->isa('Sub::Meta');
275              
276 37 100       102 if ($self->has_subname) {
277 23 100       58 return unless $self->subname eq $other->subname
278             }
279             else {
280 14 100       32 return if $other->has_subname;
281             }
282              
283 30 100       75 return unless $self->is_method eq $other->is_method;
284              
285 25 100       57 return unless $self->parameters->is_same_interface($other->parameters);
286              
287 19 100       53 return unless $self->returns->is_same_interface($other->returns);
288              
289 14         35 return !!1;
290             }
291              
292             sub is_strict_same_interface;
293             *is_strict_same_interface = \&is_same_interface;
294              
295             sub is_relaxed_same_interface {
296 39     39 1 80 my ($self, $other) = @_;
297              
298 39 100 100     246 return unless Scalar::Util::blessed($other) && $other->isa('Sub::Meta');
299              
300 37 100       80 if ($self->has_subname) {
301 23 100       48 return unless $self->subname eq $other->subname
302             }
303              
304 34 100       84 return unless $self->is_method eq $other->is_method;
305              
306 29 100       60 return unless $self->parameters->is_relaxed_same_interface($other->parameters);
307              
308 24 100       49 return unless $self->returns->is_relaxed_same_interface($other->returns);
309              
310 21         49 return !!1;
311             }
312              
313             sub is_same_interface_inlined {
314 11     11 1 23 my ($self, $v) = @_;
315              
316 11         16 my @src;
317              
318 11         68 push @src => sprintf("Scalar::Util::blessed(%s) && %s->isa('Sub::Meta')", $v, $v);
319              
320 11 100       41 push @src => $self->has_subname ? sprintf("'%s' eq %s->subname", $self->subname, $v)
321             : sprintf('!%s->has_subname', $v);
322              
323 11         30 push @src => sprintf("'%s' eq %s->is_method", $self->is_method, $v);
324              
325 11         34 push @src => $self->parameters->is_same_interface_inlined(sprintf('%s->parameters', $v));
326              
327 11         32 push @src => $self->returns->is_same_interface_inlined(sprintf('%s->returns', $v));
328              
329 11         2532 return join "\n && ", @src;
330             }
331              
332             sub is_strict_same_interface_inlined;
333             *is_strict_same_interface_inlined = \&is_same_interface_inlined;
334              
335             sub is_relaxed_same_interface_inlined {
336 15     15 1 36 my ($self, $v) = @_;
337              
338 15         21 my @src;
339              
340 15         64 push @src => sprintf("Scalar::Util::blessed(%s) && %s->isa('Sub::Meta')", $v, $v);
341              
342 15 100       35 push @src => sprintf("'%s' eq %s->subname", $self->subname, $v) if $self->has_subname;
343              
344 15         38 push @src => sprintf("'%s' eq %s->is_method", $self->is_method, $v);
345              
346 15         38 push @src => $self->parameters->is_relaxed_same_interface_inlined(sprintf('%s->parameters', $v));
347              
348 15         37 push @src => $self->returns->is_relaxed_same_interface_inlined(sprintf('%s->returns', $v));
349              
350 15         1604 return join "\n && ", @src;
351             }
352              
353             sub error_message {
354 22     22 1 37 my ($self, $other) = @_;
355              
356 22 100 100     161 return sprintf('other must be Sub::Meta. got: %s', $other // 'Undef')
      100        
357             unless Scalar::Util::blessed($other) && $other->isa('Sub::Meta');
358              
359 19 100       48 if ($self->has_subname) {
360 7 100       18 return sprintf('invalid subname. got: %s, expected: %s', $other->subname, $self->subname)
361             unless $self->subname eq $other->subname
362             }
363             else {
364 12 100       22 return sprintf('should not have subname. got: %s', $other->subname) if $other->has_subname;
365             }
366              
367 16 100       33 return 'invalid method'
368             unless $self->is_method eq $other->is_method;
369              
370 14 100       32 return "invalid parameters: " . $self->parameters->error_message($other->parameters)
371             unless $self->parameters->is_same_interface($other->parameters);
372              
373 10 100       20 return "invalid returns: " . $self->returns->error_message($other->returns)
374             unless $self->returns->is_same_interface($other->returns);
375              
376 7         15 return '';
377             }
378              
379             sub relaxed_error_message {
380 23     23 1 41 my ($self, $other) = @_;
381              
382 23 100 100     122 return sprintf('other must be Sub::Meta. got: %s', $other // 'Undef')
      100        
383             unless Scalar::Util::blessed($other) && $other->isa('Sub::Meta');
384              
385 20 100       47 if ($self->has_subname) {
386 7 100       14 return sprintf('invalid subname. got: %s, expected: %s', $other->subname, $self->subname)
387             unless $self->subname eq $other->subname
388             }
389              
390 18 100       41 return 'invalid method'
391             unless $self->is_method eq $other->is_method;
392              
393 16 100       45 return "invalid parameters: " . $self->parameters->relaxed_error_message($other->parameters)
394             unless $self->parameters->is_relaxed_same_interface($other->parameters);
395              
396 12 100       30 return "invalid returns: " . $self->returns->relaxed_error_message($other->returns)
397             unless $self->returns->is_relaxed_same_interface($other->returns);
398              
399 10         20 return '';
400             }
401              
402             sub display {
403 36     36 1 76 my $self = shift;
404              
405 36 100       53 my $keyword = $self->is_method ? 'method' : 'sub';
406 36         71 my $subname = $self->subname;
407              
408 36         46 my $s = $keyword;
409 36 100       68 $s .= ' ' . $subname if $subname;
410 36         58 $s .= '('. $self->parameters->display .')';
411 36         67 $s .= ' => ' . $self->returns->display;
412 36         228 return $s;
413             }
414              
415             1;
416             __END__