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   5344714 use 5.010;
  29         246  
3 29     29   172 use strict;
  29         59  
  29         547  
4 29     29   151 use warnings;
  29         84  
  29         1124  
5              
6             our $VERSION = "0.14";
7              
8 29     29   168 use Carp ();
  29         58  
  29         554  
9 29     29   181 use Scalar::Util ();
  29         66  
  29         495  
10 29     29   11497 use Sub::Identify ();
  29         29388  
  29         577  
11 29     29   11302 use Sub::Util ();
  29         6966  
  29         652  
12 29     29   14779 use attributes ();
  29         35019  
  29         820  
13              
14 29     29   13200 use Sub::Meta::Parameters;
  29         105  
  29         958  
15 29     29   14014 use Sub::Meta::Returns;
  29         84  
  29         1319  
16              
17             BEGIN {
18             # for Pure Perl
19 29     29   1202 $ENV{PERL_SUB_IDENTIFY_PP} = $ENV{PERL_SUB_META_PP}; ## no critic (RequireLocalizedPunctuationVars)
20             }
21              
22             use overload
23 29         239 fallback => 1,
24             eq => \&is_same_interface
25 29     29   216 ;
  29         63  
26              
27 175     175 1 827 sub parameters_class { return 'Sub::Meta::Parameters' }
28 178     178 1 791 sub returns_class { return 'Sub::Meta::Returns' }
29              
30 9     9   130 sub _croak { require Carp; goto &Carp::croak }
  9         1209  
31              
32             sub new {
33 200     200 1 337560 my ($class, @args) = @_;
34              
35             my %args = @args == 1 && (ref $args[0]||"") ne "HASH" ? _croak "single arg must be hashref"
36 200 100 100     1408 : @args == 1 ? %{$args[0]}
  91 100       444  
37             : @args;
38              
39 196         534 my $self = bless \%args => $class;
40              
41 196 100       605 $self->set_sub(delete $args{sub}) if exists $args{sub}; # build subinfo
42 196 100       652 $self->set_subname(delete $args{subname}) if exists $args{subname};
43 196 100       593 $self->set_stashname(delete $args{stashname}) if exists $args{stashname};
44 196 100       494 $self->set_fullname(delete $args{fullname}) if exists $args{fullname};
45              
46 196         665 $self->set_is_method($self->_normalize_args_is_method(\%args));
47 196         614 $self->set_parameters($self->_normalize_args_parameters(\%args));
48 196         1038 $self->set_returns($args{returns});
49              
50             # cleaning
51 196         469 delete $args{args};
52 196         353 delete $args{slurpy};
53 196         373 delete $args{invocant};
54 196         293 delete $args{nshift};
55              
56 196         613 return $self;
57             }
58              
59             sub _normalize_args_is_method {
60 196     196   391 my ($self, $args) = @_;
61              
62 196 100       1028 return !!$args->{invocant} if exists $args->{invocant};
63 192 100       508 return !!$args->{nshift} if exists $args->{nshift};
64 190 100 100     893 return !!$args->{parameters}{nshift} if exists $args->{parameters} && exists $args->{parameters}{nshift};
65 189 100 100     544 return !!$args->{parameters}{invocant} if exists $args->{parameters} && exists $args->{parameters}{invocant};
66 188 100       516 return !!$args->{is_method} if exists $args->{is_method};
67 168         674 return !!0;
68             }
69              
70             sub _normalize_args_parameters {
71 196     196   381 my ($self, $args) = @_;
72              
73 196 100       488 if (exists $args->{parameters}) {
74 27         113 return $args->{parameters};
75             }
76             else {
77             my $nshift = exists $args->{nshift} ? $args->{nshift}
78 169 100       552 : $self->is_method ? 1
    100          
79             : 0;
80              
81 169         306 my $parameters;
82 169 100       507 $parameters->{args} = $args->{args} if exists $args->{args};
83 169 100       401 $parameters->{slurpy} = $args->{slurpy} if exists $args->{slurpy};
84 169 100       383 $parameters->{invocant} = $args->{invocant} if exists $args->{invocant};
85 169         364 $parameters->{nshift} = $nshift;
86 169         591 return $parameters;
87             }
88             }
89              
90 526     526 1 18607 sub sub() :method { my $self = shift; return $self->{sub} } ## no critic (ProhibitBuiltinHomonyms)
  526         2103  
91 267   100 267 1 6170 sub subname() { my $self = shift; return $self->subinfo->[1] // '' }
  267         476  
92 49   100 49 1 5482 sub stashname() { my $self = shift; return $self->subinfo->[0] // '' }
  49         96  
93             sub fullname() {
94 40     40 1 5212 my $self = shift;
95 40         95 my $s = '';
96 40 100       103 $s .= $self->stashname . '::' if $self->has_stashname;
97 40 100       110 $s .= $self->subname if $self->has_subname;
98 40         115 return $s;
99             }
100              
101             sub subinfo() {
102 718     718 1 6598 my $self = shift;
103 718 100       4291 return $self->{subinfo} if $self->{subinfo};
104 93         268 $self->{subinfo} = $self->_build_subinfo;
105 93         356 return $self->{subinfo};
106             }
107              
108 55   100 55 1 8817 sub file() { my $self = shift; return $self->{file} ||= $self->_build_file }
  55         242  
109 55   100 55 1 4493 sub line() { my $self = shift; return $self->{line} ||= $self->_build_line }
  55         274  
110 83   100 83 1 4439 sub prototype() :method { my $self = shift; return $self->{prototype} ||= $self->_build_prototype } ## no critic (ProhibitBuiltinHomonyms)
  83         332  
111 83   100 83 1 4600 sub attribute() { my $self = shift; return $self->{attribute} ||= $self->_build_attribute }
  83         366  
112 58   100 58 1 6613 sub is_constant() { my $self = shift; return $self->{is_constant} ||= !!$self->_build_is_constant }
  58         286  
113 562     562 1 8317 sub is_method() { my $self = shift; return !!$self->{is_method} }
  562         2904  
114 928     928 1 7759 sub parameters() { my $self = shift; return $self->{parameters} }
  928         4735  
115 522     522 1 10903 sub returns() { my $self = shift; return $self->{returns} }
  522         3103  
116              
117 15     15 1 2956 sub args() { my $self = shift; return $self->parameters->args }
  15         37  
118 2     2 1 9 sub all_args() { my $self = shift; return $self->parameters->all_args }
  2         7  
119 12     12 1 25 sub slurpy() { my $self = shift; return $self->parameters->slurpy }
  12         31  
120 11     11 1 21 sub nshift() { my $self = shift; return $self->parameters->nshift }
  11         24  
121 11     11 1 19 sub invocant() { my $self = shift; return $self->parameters->invocant }
  11         33  
122 1     1 1 3 sub invocants() { my $self = shift; return $self->parameters->invocants }
  1         3  
123              
124 120     120 1 5147 sub has_sub() { my $self = shift; return defined $self->{sub} }
  120         450  
125 262     262 1 7721 sub has_subname() { my $self = shift; return defined $self->subinfo->[1] }
  262         589  
126 67     67 1 5120 sub has_stashname() { my $self = shift; return defined $self->subinfo->[0] }
  67         133  
127 27     27 1 4890 sub has_prototype() { my $self = shift; return !!$self->prototype } # after build_prototype
  27         67  
128 27     27 1 5166 sub has_attribute() { my $self = shift; return !!$self->attribute } # after build_attribute
  27         62  
129 27     27 1 5284 sub has_file() { my $self = shift; return defined $self->{file} }
  27         74  
130 27     27 1 4867 sub has_line() { my $self = shift; return defined $self->{line} }
  27         87  
131              
132             sub set_sub {
133 28     28 1 106 my ($self, $v) = @_;
134 28         243 $self->{sub} = $v;
135 28         113 Scalar::Util::weaken($self->{sub});
136              
137             # rebuild
138 28         104 for (qw/subinfo file line prototype attribute is_constant/) {
139 168         1154 delete $self->{$_};
140 168         419 $self->$_;
141             }
142 28         69 return $self;
143             }
144              
145 57     57 1 189 sub set_subname { my ($self, $v) = @_; $self->{subinfo}[1] = $v; return $self }
  57         234  
  57         106  
146 2     2 1 15 sub set_stashname { my ($self, $v) = @_; $self->{subinfo}[0] = $v; return $self }
  2         6  
  2         8  
147             sub set_fullname {
148 14     14 1 8984 my ($self, $v) = @_;
149 14 100       139 $self->set_subinfo($v =~ m!^(.+)::([^:]+)$! ? [$1, $2] : []);
150 14         52 return $self;
151             }
152             sub set_subinfo {
153 25     25 1 2783 my ($self, $args) = @_;
154 25         85 $self->{subinfo} = [ $args->[0], $args->[1] ];
155 22         59 return $self;
156             }
157              
158 1     1 1 8 sub set_file { my ($self, $v) = @_; $self->{file} = $v; return $self }
  1         3  
  1         5  
159 1     1 1 7 sub set_line { my ($self, $v) = @_; $self->{line} = $v; return $self }
  1         6  
  1         4  
160 1     1 1 7 sub set_is_constant { my ($self, $v) = @_; $self->{is_constant} = $v; return $self }
  1         3  
  1         4  
161 3     3 1 12 sub set_prototype { my ($self, $v) = @_; $self->{prototype} = $v; return $self }
  3         8  
  3         8  
162 3     3 1 53 sub set_attribute { my ($self, $v) = @_; $self->{attribute} = $v; return $self }
  3         9  
  3         9  
163 198     198 1 451 sub set_is_method { my ($self, $v) = @_; $self->{is_method} = $v; return $self }
  198         499  
  198         338  
164              
165             sub set_parameters {
166 203     203 1 2968 my ($self, @args) = @_;
167 203         363 my $v = $args[0];
168 203 100       720 if (Scalar::Util::blessed($v)) {
169 28 100       178 if ($v->isa('Sub::Meta::Parameters')) {
170 27         80 $self->{parameters} = $v
171             }
172             else {
173 1         5 _croak('object must be Sub::Meta::Parameters');
174             }
175             }
176             else {
177 175         494 $self->{parameters} = $self->parameters_class->new(@args);
178             }
179 200         420 return $self
180             }
181              
182             sub set_args {
183 3     3 1 14 my ($self, $args) = @_;
184 3         10 $self->parameters->set_args($args);
185 3         17 return $self;
186             }
187              
188             sub set_slurpy {
189 3     3 1 15 my ($self, @args) = @_;
190 3         9 $self->parameters->set_slurpy(@args);
191 3         14 return $self;
192             }
193              
194             sub set_nshift {
195 3     3 1 27 my ($self, $v) = @_;
196 3 100 100     8 if ($self->is_method && $v == 0) {
197 1         5 _croak 'nshift of method cannot be zero';
198             }
199 2         7 $self->parameters->set_nshift($v);
200 2         5 return $self;
201             }
202              
203             sub set_invocant {
204 1     1 1 4 my ($self, $v) = @_;
205 1         3 $self->parameters->set_invocant($v);
206 1         4 return $self;
207             }
208              
209             sub set_returns {
210 199     199 1 660 my ($self, @args) = @_;
211 199         353 my $v = $args[0];
212 199 100 100     804 if (Scalar::Util::blessed($v) && $v->isa('Sub::Meta::Returns')) {
213 21         65 $self->{returns} = $v
214             }
215             else {
216 178         659 $self->{returns} = $self->returns_class->new(@args);
217             }
218 199         403 return $self
219             }
220              
221             sub _build_subinfo {
222 93     93   166 my $self = shift;
223 93 100       250 return [] unless $self->has_sub;
224 28         96 my @info = Sub::Identify::get_code_info($self->sub);
225 28 100       139 return [ $info[0], $info[1] eq '__ANON__' ? undef : $info[1] ];
226             }
227              
228 46 100   46   85 sub _build_file { my $self = shift; return $self->sub ? (Sub::Identify::get_code_location($self->sub))[0] : undef }
  46         105  
229 46 100   46   82 sub _build_line { my $self = shift; return $self->sub ? (Sub::Identify::get_code_location($self->sub))[1] : undef }
  46         100  
230 55 100   55   97 sub _build_is_constant { my $self = shift; return $self->sub ? Sub::Identify::is_sub_constant($self->sub) : undef }
  55         116  
231 72 100   72   128 sub _build_prototype { my $self = shift; return $self->sub ? Sub::Util::prototype($self->sub) : undef }
  72         209  
232 66 100   66   111 sub _build_attribute { my $self = shift; return $self->sub ? [ attributes::get($self->sub) ] : undef }
  66         161  
233              
234             sub apply_subname {
235 3     3 1 7 my ($self, $subname) = @_;
236 3 100       8 _croak 'apply_subname requires subroutine reference' unless $self->sub;
237 2         6 $self->set_subname($subname);
238 2         5 Sub::Util::set_subname($self->fullname, $self->sub);
239 2         7 return $self;
240             }
241              
242             sub apply_prototype {
243 3     3 1 10 my ($self, $prototype) = @_;
244 3 100       6 _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         6 return $self;
248             }
249              
250             sub apply_attribute {
251 4     4 1 36 my ($self, @attribute) = @_;
252 4 100       8 _croak 'apply_attribute requires subroutine reference' unless $self->sub;
253             {
254 29     29   74806 no warnings qw(misc); ## no critic (ProhibitNoWarnings)
  29         73  
  29         32498  
  3         6  
255 3         7 attributes->import($self->stashname, $self->sub, @attribute);
256             }
257 2         175 $self->set_attribute($self->_build_attribute);
258 2         6 return $self;
259             }
260              
261             sub apply_meta {
262 1     1 1 6 my ($self, $other) = @_;
263              
264 1         4 $self->apply_subname($other->subname);
265 1         4 $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 89 my ($self, $other) = @_;
273              
274 39 100 100     335 return unless Scalar::Util::blessed($other) && $other->isa('Sub::Meta');
275              
276 37 100       134 if ($self->has_subname) {
277 23 100       64 return unless $self->subname eq $other->subname
278             }
279             else {
280 14 100       32 return if $other->has_subname;
281             }
282              
283 30 100       106 return unless $self->is_method eq $other->is_method;
284              
285 25 100       72 return unless $self->parameters->is_same_interface($other->parameters);
286              
287 19 100       58 return unless $self->returns->is_same_interface($other->returns);
288              
289 14         38 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 88 my ($self, $other) = @_;
297              
298 39 100 100     289 return unless Scalar::Util::blessed($other) && $other->isa('Sub::Meta');
299              
300 37 100       104 if ($self->has_subname) {
301 23 100       57 return unless $self->subname eq $other->subname
302             }
303              
304 34 100       89 return unless $self->is_method eq $other->is_method;
305              
306 29 100       73 return unless $self->parameters->is_relaxed_same_interface($other->parameters);
307              
308 24 100       70 return unless $self->returns->is_relaxed_same_interface($other->returns);
309              
310 21         56 return !!1;
311             }
312              
313             sub is_same_interface_inlined {
314 11     11 1 27 my ($self, $v) = @_;
315              
316 11         22 my @src;
317              
318 11         87 push @src => sprintf("Scalar::Util::blessed(%s) && %s->isa('Sub::Meta')", $v, $v);
319              
320 11 100       55 push @src => $self->has_subname ? sprintf("'%s' eq %s->subname", $self->subname, $v)
321             : sprintf('!%s->has_subname', $v);
322              
323 11         38 push @src => sprintf("'%s' eq %s->is_method", $self->is_method, $v);
324              
325 11         44 push @src => $self->parameters->is_same_interface_inlined(sprintf('%s->parameters', $v));
326              
327 11         41 push @src => $self->returns->is_same_interface_inlined(sprintf('%s->returns', $v));
328              
329 11         3288 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 56 my ($self, $v) = @_;
337              
338 15         27 my @src;
339              
340 15         80 push @src => sprintf("Scalar::Util::blessed(%s) && %s->isa('Sub::Meta')", $v, $v);
341              
342 15 100       55 push @src => sprintf("'%s' eq %s->subname", $self->subname, $v) if $self->has_subname;
343              
344 15         56 push @src => sprintf("'%s' eq %s->is_method", $self->is_method, $v);
345              
346 15         52 push @src => $self->parameters->is_relaxed_same_interface_inlined(sprintf('%s->parameters', $v));
347              
348 15         47 push @src => $self->returns->is_relaxed_same_interface_inlined(sprintf('%s->returns', $v));
349              
350 15         2069 return join "\n && ", @src;
351             }
352              
353             sub error_message {
354 22     22 1 67 my ($self, $other) = @_;
355              
356 22 100 100     204 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       60 if ($self->has_subname) {
360 7 100       22 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       28 return sprintf('should not have subname. got: %s', $other->subname) if $other->has_subname;
365             }
366              
367 16 100       53 return 'invalid method'
368             unless $self->is_method eq $other->is_method;
369              
370 14 100       38 return "invalid parameters: " . $self->parameters->error_message($other->parameters)
371             unless $self->parameters->is_same_interface($other->parameters);
372              
373 10 100       28 return "invalid returns: " . $self->returns->error_message($other->returns)
374             unless $self->returns->is_same_interface($other->returns);
375              
376 7         23 return '';
377             }
378              
379             sub relaxed_error_message {
380 23     23 1 51 my ($self, $other) = @_;
381              
382 23 100 100     173 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       64 if ($self->has_subname) {
386 7 100       18 return sprintf('invalid subname. got: %s, expected: %s', $other->subname, $self->subname)
387             unless $self->subname eq $other->subname
388             }
389              
390 18 100       44 return 'invalid method'
391             unless $self->is_method eq $other->is_method;
392              
393 16 100       40 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         25 return '';
400             }
401              
402             sub display {
403 36     36 1 98 my $self = shift;
404              
405 36 100       73 my $keyword = $self->is_method ? 'method' : 'sub';
406 36         91 my $subname = $self->subname;
407              
408 36         58 my $s = $keyword;
409 36 100       103 $s .= ' ' . $subname if $subname;
410 36         73 $s .= '('. $self->parameters->display .')';
411 36         91 $s .= ' => ' . $self->returns->display;
412 36         272 return $s;
413             }
414              
415             1;
416             __END__