File Coverage

blib/lib/MooseX/Method/Signatures/Meta/Method.pm
Criterion Covered Total %
statement 212 214 99.0
branch 62 66 93.9
condition 5 11 45.4
subroutine 48 88 54.5
pod 0 2 0.0
total 327 381 85.8


line stmt bran cond sub pod time code
1             package MooseX::Method::Signatures::Meta::Method;
2             # ABSTRACT: Provides the metaclass for methods with signatures
3             $MooseX::Method::Signatures::Meta::Method::VERSION = '0.48';
4 29     39   505687 use Moose;
  29         436771  
  29         236  
5 29     29   174116 use Carp qw/cluck/;
  29         60  
  29         1606  
6 29     29   16279 use Context::Preserve;
  29         11439  
  29         1573  
7 29     29   17987 use Parse::Method::Signatures 1.003014;
  29         9237426  
  29         1277  
8 29     29   19798 use Parse::Method::Signatures::TypeConstraint;
  29         1543033  
  29         1344  
9 29     29   253 use Scalar::Util qw/weaken/;
  29         46  
  29         2115  
10 29     29   181 use Moose::Util qw/does_role/;
  29         49  
  29         262  
11 29     29   8226 use Moose::Util::TypeConstraints;
  29         57  
  29         270  
12 29     29   73099 use MooseX::Meta::TypeConstraint::ForceCoercion;
  29         406061  
  29         1200  
13 29     29   246 use MooseX::Types::Util qw/has_available_type_export/;
  29         45  
  29         2017  
14 29     29   58714 use MooseX::Types::Structured qw/Tuple Dict Optional slurpy/;
  29         10102670  
  29         221  
15 29     29   13733 use MooseX::Types::Moose qw/ArrayRef Str Maybe Object Any CodeRef Bool/;
  29         55  
  29         263  
16 29     29   167936 use MooseX::Method::Signatures::Types qw/Injections Params/;
  29         108  
  29         212  
17 29     29   40587 use aliased 'Parse::Method::Signatures::Param::Named';
  29         663  
  29         230  
18 29     29   337135 use aliased 'Parse::Method::Signatures::Param::Placeholder';
  29         600  
  29         157  
19              
20 29     29   174225 use namespace::autoclean;
  29         63  
  29         237  
21              
22             extends 'Moose::Meta::Method';
23              
24             has signature => (
25             is => 'ro',
26             isa => Str,
27             default => '(@)',
28             required => 1,
29             );
30              
31             has parsed_signature => (
32             is => 'ro',
33             isa => class_type('Parse::Method::Signatures::Sig'),
34             lazy => 1,
35             builder => '_build_parsed_signature',
36             );
37              
38             sub _parsed_signature {
39 0     0   0 cluck '->_parsed_signature is deprecated. use ->parsed_signature instead.';
40 0         0 shift->parsed_signature;
41             }
42              
43             has _lexicals => (
44             is => 'ro',
45             isa => ArrayRef[Str],
46             lazy => 1,
47             builder => '_build__lexicals',
48             );
49              
50             has injectable_code => (
51             is => 'ro',
52             isa => Str,
53             lazy => 1,
54             builder => '_build_injectable_code',
55             );
56              
57             has _positional_args => (
58             is => 'ro',
59             isa => ArrayRef,
60             lazy => 1,
61             builder => '_build__positional_args',
62             );
63              
64             has _named_args => (
65             is => 'ro',
66             isa => ArrayRef,
67             lazy => 1,
68             builder => '_build__named_args',
69             );
70              
71             has _has_slurpy_positional => (
72             is => 'rw',
73             isa => Bool,
74             );
75              
76             has type_constraint => (
77             is => 'ro',
78             isa => class_type('Moose::Meta::TypeConstraint'),
79             lazy => 1,
80             builder => '_build_type_constraint',
81             );
82              
83             has return_signature => (
84             is => 'ro',
85             isa => Str,
86             predicate => 'has_return_signature',
87             );
88              
89             has _return_type_constraint => (
90             is => 'ro',
91             isa => class_type('Moose::Meta::TypeConstraint'),
92             lazy => 1,
93             builder => '_build__return_type_constraint',
94             );
95              
96             has actual_body => (
97             is => 'ro',
98             isa => CodeRef,
99             predicate => '_has_actual_body',
100             );
101              
102             has prototype_injections => (
103             is => 'rw',
104             isa => Injections,
105             trigger => \&_parse_prototype_injections
106             );
107              
108             has _parsed_prototype_injections => (
109             is => 'ro',
110             isa => Params,
111             predicate => '_has_parsed_prototype_injections',
112             writer => '_set_parsed_prototype_injections',
113             );
114              
115             before actual_body => sub {
116             my ($self) = @_;
117             confess "method doesn't have an actual body yet"
118             unless $self->_has_actual_body;
119             };
120              
121             around name => sub {
122             my ($next, $self) = @_;
123             my $ret = $self->$next;
124             confess "method doesn't have a name yet"
125             unless defined $ret;
126             return $ret;
127             };
128              
129             sub _wrapped_body {
130 313     313   1108 my ($class, $self, %args) = @_;
131              
132 313 100       1000 if (exists $args{return_signature}) {
133             return sub {
134 3     3   1685 my @args = ${ $self }->validate(\@_);
  3         17  
135 2         22 return preserve_context { ${ $self }->actual_body->(@args) }
  2         10  
136             after => sub {
137 2 100       18 if (defined (my $msg = ${ $self }->_return_type_constraint->validate(\@_))) {
  2         93  
138 1         2852 confess $msg;
139             }
140 2         23 };
141 4         54 };
142             }
143              
144 309         415 my $actual_body;
145             return sub {
146 133     133   136098 @_ = ${ $self }->validate(\@_);
  133     115   597  
        101      
        90      
        74      
        60      
        91      
        32      
        32      
        32      
        30      
        32      
        30      
        32      
        32      
        30      
        32      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
147 88   66     380 $actual_body ||= ${ $self }->actual_body;
  67         431  
148 88         140 goto &{ $actual_body };
  88         442  
149 309         1989 };
150              
151             }
152              
153             around wrap => sub {
154             my $orig = shift;
155             my $self;
156             my ($class, $code, %args);
157             if (ref $_[1]) {
158             ($class, $code, %args) = @_;
159             } else {
160             ($class, %args) = @_;
161             $code = delete $args{body};
162             }
163              
164             my $wrapped = $class->_wrapped_body(\$self, %args);
165             $self = $class->$orig($wrapped, %args, $code ? (actual_body => $code) : ());
166              
167             # Vivify the type constraints so TC lookups happen before namespace::clean
168             # removes them
169             $self->type_constraint;
170             $self->_return_type_constraint if $self->has_return_signature;
171              
172             weaken($self->{associated_metaclass})
173             if $self->{associated_metaclass};
174              
175             return $self;
176             };
177              
178             sub reify {
179 1     1 0 670 my $self = shift;
180 1         3 my %args = @_;
181              
182 1         2 my %other_args = %{$self};
  1         11  
183 1         3 delete $other_args{body};
184 1         1 delete $other_args{actual_body};
185              
186 1   33     9 my $body = delete $args{body} || delete $args{actual_body} || $self->body;
187 1         5 my %final_args = (%other_args, %args);
188              
189 1         7 return $self->meta->name->wrap($body, %final_args);
190             }
191              
192             sub _build_parsed_signature {
193 158     158   249 my ($self) = @_;
194 158         5969 return Parse::Method::Signatures->signature(
195             input => $self->signature,
196             from_namespace => $self->package_name,
197             );
198             }
199              
200             sub _build__return_type_constraint {
201 2     2   3 my ($self) = @_;
202 2 50       74 confess 'no return type constraint'
203             unless $self->has_return_signature;
204              
205 2         65 my $parser = Parse::Method::Signatures->new(
206             input => $self->return_signature,
207             from_namespace => $self->package_name,
208             );
209              
210 2         5251 my $param = $parser->_param_typed({});
211 2 50       2530 confess 'failed to parse return value type constraint'
212             unless exists $param->{type_constraints};
213              
214 2         61 return Tuple[$param->{type_constraints}->tc];
215             }
216              
217             sub _param_to_spec {
218 210     210   6652 my ($self, $param) = @_;
219              
220 210         704 my $tc = Any;
221             {
222             # Ensure errors get reported from the right place
223 210         22813 local $Carp::Internal{'MooseX::Method::Signatures::Meta::Method'} = 1;
  210         756  
224 210         497 local $Carp::Internal{'Moose::Meta::Method::Delegation'} = 1;
225 210         487 local $Carp::Internal{'Moose::Meta::Method::Accessor'} = 1;
226 210         476 local $Carp::Internal{'MooseX::Method::Signatures'} = 1;
227 210         551 local $Carp::Internal{'Devel::Declare'} = 1;
228 210 100       9860 $tc = $param->meta_type_constraint
229             if $param->has_type_constraints;
230             }
231              
232 210 100       86508 if ($param->has_constraints) {
233 31         1106 my $cb = join ' && ', map { "sub {${_}}->(\\\@_)" } $param->constraints;
  34         362  
234 31         3265 my $code = eval "sub {${cb}}";
235 31         242 $tc = subtype({ as => $tc, where => $code });
236             }
237              
238 210         27723 my %spec;
239 210 100       6167 if ($param->sigil ne '$') {
240 23         212 $spec{slurpy} = 1;
241 23         258 $tc = slurpy ArrayRef[$tc];
242             }
243              
244 209 100       87687 $spec{tc} = $param->required
245             ? $tc
246             : Optional[$tc];
247              
248 209 100       247691 $spec{default} = $param->default_value
249             if $param->has_default_value;
250              
251 209 100       9566 if ($param->has_traits) {
252 34         234 for my $trait (@{ $param->param_traits }) {
  34         1174  
253 34 50       390 next unless $trait->[1] eq 'coerce';
254 34         113 $spec{coerce} = 1;
255             }
256             }
257              
258 209         1415 return \%spec;
259             }
260              
261             sub _parse_prototype_injections {
262 6     6   28111 my $self = shift;
263              
264 6         12 my @params;
265 6         7 for my $inject (@{ $self->prototype_injections }) {
  6         210  
266 6         7 my $param;
267 6         8 eval {
268 6         39 $param = Parse::Method::Signatures->param($inject);
269             };
270              
271 6 50 33     279243 confess "There was a problem parsing the prototype injection '$inject': $@"
272             if $@ || !defined $param;
273              
274 6         27 push @params, $param;
275             }
276              
277 6         22 my @return = reverse @params;
278 6         284 $self->_set_parsed_prototype_injections(\@return);
279             }
280              
281             sub _build__lexicals {
282 152     152   243 my ($self) = @_;
283 152         5794 my ($sig) = $self->parsed_signature;
284              
285 152         264 my @lexicals;
286              
287 152 100       7240 if ($self->_has_parsed_prototype_injections) {
288 3         127 push @lexicals, $_->variable_name
289 3         7 for @{ $self->_parsed_prototype_injections };
290             }
291              
292 152 100       5843 push @lexicals, $sig->has_invocant
293             ? $sig->invocant->variable_name
294             : '$self';
295              
296             push @lexicals,
297             (does_role($_, Placeholder)
298             ? 'undef'
299             : $_->variable_name)
300 152 100       2677 for (($sig->has_positional_params ? $sig->positional_params : ()),
    100          
    100          
301             ($sig->has_named_params ? $sig->named_params : ()));
302              
303 152         132794 return \@lexicals;
304             }
305              
306             sub _build_injectable_code {
307 152     152   258 my ($self) = @_;
308 152         270 my $vars = join q{,}, @{ $self->_lexicals };
  152         5767  
309 152         6151 return "my (${vars}) = \@_;";
310             }
311              
312             sub _build__positional_args {
313 158     158   287 my ($self) = @_;
314 158         6234 my $sig = $self->parsed_signature;
315              
316 157         269 my @positional;
317 157 100       8017 if ($self->_has_parsed_prototype_injections) {
318 3         10 push @positional, map {
319 3         101 $self->_param_to_spec($_)
320 3         4 } @{ $self->_parsed_prototype_injections };
321             }
322              
323 157 100       5673 push @positional, $sig->has_invocant
324             ? $self->_param_to_spec($sig->invocant)
325             : { tc => Object };
326              
327 157         16742 my $slurpy = 0;
328 157 100       775 if ($sig->has_positional_params) {
329 101         15202 for my $param ($sig->positional_params) {
330 126         8299 my $spec = $self->_param_to_spec($param);
331 125 100 50     536 $slurpy ||= 1 if $spec->{slurpy};
332 125         371 push @positional, $spec;
333             }
334             }
335              
336 156         9571 $self->_has_slurpy_positional($slurpy);
337 156         6151 return \@positional;
338             }
339              
340             sub _build__named_args {
341 156     156   272 my ($self) = @_;
342 156         6062 my $sig = $self->parsed_signature;
343              
344             # triggering building of positionals before named params is important
345             # because the latter needs to know if there have been any slurpy
346             # positionals to report errors
347 156         5750 $self->_positional_args;
348              
349 156         204 my @named;
350              
351 156 100       706 if ($sig->has_named_params) {
352 52 100       9198 confess 'Named parameters cannot be combined with slurpy positionals'
353             if $self->_has_slurpy_positional;
354 51         253 for my $param ($sig->named_params) {
355 61         5533 push @named, $param->label => $self->_param_to_spec($param);
356             }
357             }
358              
359 155         11090 return \@named;
360             }
361              
362             sub _build_type_constraint {
363 158     158   281 my ($self) = @_;
364 158         337 my ($positional, $named) = map { $self->$_ } map { "_${_}_args" } qw/positional named/;
  314         12327  
  316         869  
365              
366 281         710 my $tc = Tuple[
367 155 100       337 Tuple[ map { $_->{tc} } @{ $positional } ],
  122         498  
368 155         387 Dict[ map { ref $_ ? $_->{tc} : $_ } @{ $named } ],
  155         611  
369             ];
370              
371             my $coerce_param = sub {
372 313     313   416 my ($spec, $value) = @_;
373 313 100       1203 return $value unless exists $spec->{coerce};
374 7         51 return $spec->{tc}->coerce($value);
375 155         1520308 };
376              
377 155         501 my %named = @{ $named };
  155         479  
378              
379             coerce $tc,
380             from ArrayRef,
381             via {
382 136     136   566068 my (@positional_args, %named_args);
383              
384 136         290 my $i = 0;
385 136         173 for my $param (@{ $positional }) {
  136         347  
386 260         555 push @positional_args, map { $coerce_param->($param, $_) }
  291         1314  
387 291 100       325 $#{ $_ } < $i
    100          
388             ? (exists $param->{default} ? eval $param->{default} : ())
389             : $_->[$i];
390 291         3419 $i++;
391             }
392              
393 136 100       372 if (%named) {
  89 100       299  
394 47         94 my @rest = @{ $_ }[$i .. $#{ $_ }];
  47         147  
  47         89  
395 47 100       533 confess "Expected named arguments but didn't find an even-sized list"
396             unless @rest % 2 == 0;
397 44         126 my %rest = @rest;
398              
399 44         203 while (my ($key, $spec) = each %named) {
400 68 100       139 if (exists $rest{$key}) {
401 48         118 $named_args{$key} = $coerce_param->($spec, delete $rest{$key});
402 48         1257 next;
403             }
404              
405 20 100       82 if (exists $spec->{default}) {
406 5         325 $named_args{$key} = $coerce_param->($spec, eval $spec->{default});
407             }
408             }
409              
410 44         141 @named_args{keys %rest} = values %rest;
411             }
412             elsif ($#{ $_ } >= $i) {
413 8         16 push @positional_args, @{ $_ }[$i .. $#{ $_ }];
  8         20  
  8         14  
414             }
415              
416 133         916 return [\@positional_args, \%named_args];
417 155         632 };
418              
419 155         300244 return MooseX::Meta::TypeConstraint::ForceCoercion->new(
420             type_constraint => $tc,
421             );
422             }
423              
424             sub validate {
425 136     136 0 210 my ($self, $args) = @_;
426              
427 136         197 my @named = grep { !ref $_ } @{ $self->_named_args };
  142         304  
  136         6268  
428              
429 136         204 my $coerced;
430 136 100       5376 if (defined (my $msg = $self->type_constraint->validate($args, \$coerced))) {
431 43         201741 confess $msg;
432             }
433              
434 90         224048 return @{ $coerced->[0] }, map { $coerced->[1]->{$_} } @named;
  90         462  
  37         184  
435             }
436              
437             __PACKAGE__->meta->make_immutable;
438              
439             #pod =pod
440             #pod
441             #pod =for stopwords metaclass
442             #pod
443             #pod =cut
444              
445             1;
446              
447             __END__
448              
449             =pod
450              
451             =encoding UTF-8
452              
453             =head1 NAME
454              
455             MooseX::Method::Signatures::Meta::Method - Provides the metaclass for methods with signatures
456              
457             =head1 VERSION
458              
459             version 0.48
460              
461             =for stopwords metaclass
462              
463             =head1 AUTHOR
464              
465             Florian Ragwitz <rafl@debian.org>
466              
467             =head1 COPYRIGHT AND LICENSE
468              
469             This software is copyright (c) 2014 by Florian Ragwitz.
470              
471             This is free software; you can redistribute it and/or modify it under
472             the same terms as the Perl 5 programming language system itself.
473              
474             =cut