File Coverage

blib/lib/MooseX/Method/Signatures/Meta/Method.pm
Criterion Covered Total %
statement 213 215 99.0
branch 62 66 93.9
condition 5 11 45.4
subroutine 49 89 55.0
pod 0 2 0.0
total 329 383 85.9


line stmt bran cond sub pod time code
1             package MooseX::Method::Signatures::Meta::Method;
2             {
3             $MooseX::Method::Signatures::Meta::Method::VERSION = '0.47';
4             }
5             BEGIN {
6 28     38   38885 $MooseX::Method::Signatures::Meta::Method::AUTHORITY = 'cpan:ETHER';
7             }
8             # ABSTRACT: Provides the metaclass for methods with signatures
9              
10 28     28   1193 use Moose;
  28         501460  
  28         516  
11 28     28   210881 use Carp qw/cluck/;
  28         94  
  28         1989  
12 28     28   33105 use Context::Preserve;
  28         14729  
  28         1631  
13 28     28   28656 use Parse::Method::Signatures 1.003014;
  28         12175916  
  28         1369  
14 28     28   36820 use Parse::Method::Signatures::TypeConstraint;
  28         1943037  
  28         1362  
15 28     28   299 use Scalar::Util qw/weaken/;
  28         65  
  28         1947  
16 28     28   206 use Moose::Util qw/does_role/;
  28         61  
  28         353  
17 28     28   8719 use Moose::Util::TypeConstraints;
  28         66  
  28         300  
18 28     28   94584 use MooseX::Meta::TypeConstraint::ForceCoercion;
  28         497980  
  28         1374  
19 28     28   276 use MooseX::Types::Util qw/has_available_type_export/;
  28         111  
  28         2077  
20 28     28   33584 use MooseX::Types::Structured qw/Tuple Dict Optional slurpy/;
  28         12613367  
  28         270  
21 28     28   16122 use MooseX::Types::Moose qw/ArrayRef Str Maybe Object Any CodeRef Bool/;
  28         70  
  28         314  
22 28     28   217733 use MooseX::Method::Signatures::Types qw/Injections Params/;
  28         139  
  28         260  
23 28     28   46153 use aliased 'Parse::Method::Signatures::Param::Named';
  28         796  
  28         271  
24 28     28   422371 use aliased 'Parse::Method::Signatures::Param::Placeholder';
  28         80  
  28         248  
25              
26 28     28   214588 use namespace::autoclean;
  28         79  
  28         635  
27              
28             extends 'Moose::Meta::Method';
29              
30             has signature => (
31             is => 'ro',
32             isa => Str,
33             default => '(@)',
34             required => 1,
35             );
36              
37             has parsed_signature => (
38             is => 'ro',
39             isa => class_type('Parse::Method::Signatures::Sig'),
40             lazy => 1,
41             builder => '_build_parsed_signature',
42             );
43              
44             sub _parsed_signature {
45 0     0   0 cluck '->_parsed_signature is deprecated. use ->parsed_signature instead.';
46 0         0 shift->parsed_signature;
47             }
48              
49             has _lexicals => (
50             is => 'ro',
51             isa => ArrayRef[Str],
52             lazy => 1,
53             builder => '_build__lexicals',
54             );
55              
56             has injectable_code => (
57             is => 'ro',
58             isa => Str,
59             lazy => 1,
60             builder => '_build_injectable_code',
61             );
62              
63             has _positional_args => (
64             is => 'ro',
65             isa => ArrayRef,
66             lazy => 1,
67             builder => '_build__positional_args',
68             );
69              
70             has _named_args => (
71             is => 'ro',
72             isa => ArrayRef,
73             lazy => 1,
74             builder => '_build__named_args',
75             );
76              
77             has _has_slurpy_positional => (
78             is => 'rw',
79             isa => Bool,
80             );
81              
82             has type_constraint => (
83             is => 'ro',
84             isa => class_type('Moose::Meta::TypeConstraint'),
85             lazy => 1,
86             builder => '_build_type_constraint',
87             );
88              
89             has return_signature => (
90             is => 'ro',
91             isa => Str,
92             predicate => 'has_return_signature',
93             );
94              
95             has _return_type_constraint => (
96             is => 'ro',
97             isa => class_type('Moose::Meta::TypeConstraint'),
98             lazy => 1,
99             builder => '_build__return_type_constraint',
100             );
101              
102             has actual_body => (
103             is => 'ro',
104             isa => CodeRef,
105             predicate => '_has_actual_body',
106             );
107              
108             has prototype_injections => (
109             is => 'rw',
110             isa => Injections,
111             trigger => \&_parse_prototype_injections
112             );
113              
114             has _parsed_prototype_injections => (
115             is => 'ro',
116             isa => Params,
117             predicate => '_has_parsed_prototype_injections',
118             writer => '_set_parsed_prototype_injections',
119             );
120              
121             before actual_body => sub {
122             my ($self) = @_;
123             confess "method doesn't have an actual body yet"
124             unless $self->_has_actual_body;
125             };
126              
127             around name => sub {
128             my ($next, $self) = @_;
129             my $ret = $self->$next;
130             confess "method doesn't have a name yet"
131             unless defined $ret;
132             return $ret;
133             };
134              
135             sub _wrapped_body {
136 313     313   1700 my ($class, $self, %args) = @_;
137              
138 313 100       1498 if (exists $args{return_signature}) {
139             return sub {
140 3     3   2008 my @args = ${ $self }->validate(\@_);
  3         14  
141 2         29 return preserve_context { ${ $self }->actual_body->(@args) }
  2         17  
142             after => sub {
143 2 100       22 if (defined (my $msg = ${ $self }->_return_type_constraint->validate(\@_))) {
  2         101  
144 1         3419 confess $msg;
145             }
146 2         32 };
147 4         32 };
148             }
149              
150 309         455 my $actual_body;
151             return sub {
152 133     133   180296 @_ = ${ $self }->validate(\@_);
  133     115   703  
        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      
153 88   66     562 $actual_body ||= ${ $self }->actual_body;
  67         536  
154 88         166 goto &{ $actual_body };
  88         559  
155 309         2693 };
156              
157             }
158              
159             around wrap => sub {
160             my $orig = shift;
161             my $self;
162             my ($class, $code, %args);
163             if (ref $_[1]) {
164             ($class, $code, %args) = @_;
165             } else {
166             ($class, %args) = @_;
167             $code = delete $args{body};
168             }
169              
170             my $wrapped = $class->_wrapped_body(\$self, %args);
171             $self = $class->$orig($wrapped, %args, $code ? (actual_body => $code) : ());
172              
173             # Vivify the type constraints so TC lookups happen before namespace::clean
174             # removes them
175             $self->type_constraint;
176             $self->_return_type_constraint if $self->has_return_signature;
177              
178             weaken($self->{associated_metaclass})
179             if $self->{associated_metaclass};
180              
181             return $self;
182             };
183              
184             sub reify {
185 1     1 0 754 my $self = shift;
186 1         3 my %args = @_;
187              
188 1         3 my %other_args = %{$self};
  1         12  
189 1         3 delete $other_args{body};
190 1         4 delete $other_args{actual_body};
191              
192 1   33     24 my $body = delete $args{body} || delete $args{actual_body} || $self->body;
193 1         9 my %final_args = (%other_args, %args);
194              
195 1         9 return $self->meta->name->wrap($body, %final_args);
196             }
197              
198             sub _build_parsed_signature {
199 158     158   320 my ($self) = @_;
200 158         8027 return Parse::Method::Signatures->signature(
201             input => $self->signature,
202             from_namespace => $self->package_name,
203             );
204             }
205              
206             sub _build__return_type_constraint {
207 2     2   5 my ($self) = @_;
208 2 50       127 confess 'no return type constraint'
209             unless $self->has_return_signature;
210              
211 2         120 my $parser = Parse::Method::Signatures->new(
212             input => $self->return_signature,
213             from_namespace => $self->package_name,
214             );
215              
216 2         6912 my $param = $parser->_param_typed({});
217 2 50       3343 confess 'failed to parse return value type constraint'
218             unless exists $param->{type_constraints};
219              
220 2         89 return Tuple[$param->{type_constraints}->tc];
221             }
222              
223             sub _param_to_spec {
224 210     210   9447 my ($self, $param) = @_;
225              
226 210         813 my $tc = Any;
227             {
228             # Ensure errors get reported from the right place
229 210         31924 local $Carp::Internal{'MooseX::Method::Signatures::Meta::Method'} = 1;
  210         874  
230 210         636 local $Carp::Internal{'Moose::Meta::Method::Delegation'} = 1;
231 210         857 local $Carp::Internal{'Moose::Meta::Method::Accessor'} = 1;
232 210         633 local $Carp::Internal{'MooseX::Method::Signatures'} = 1;
233 210         663 local $Carp::Internal{'Devel::Declare'} = 1;
234 210 100       14230 $tc = $param->meta_type_constraint
235             if $param->has_type_constraints;
236             }
237              
238 210 100       125829 if ($param->has_constraints) {
239 31         1579 my $cb = join ' && ', map { "sub {${_}}->(\\\@_)" } $param->constraints;
  34         467  
240 31         5574 my $code = eval "sub {${cb}}";
241 31         280 $tc = subtype({ as => $tc, where => $code });
242             }
243              
244 210         38220 my %spec;
245 210 100       9028 if ($param->sigil ne '$') {
246 23         221 $spec{slurpy} = 1;
247 23         131 $tc = slurpy ArrayRef[$tc];
248             }
249              
250 209 100       193979 $spec{tc} = $param->required
251             ? $tc
252             : Optional[$tc];
253              
254 209 100       340737 $spec{default} = $param->default_value
255             if $param->has_default_value;
256              
257 209 100       12160 if ($param->has_traits) {
258 34         316 for my $trait (@{ $param->param_traits }) {
  34         1434  
259 34 50       393 next unless $trait->[1] eq 'coerce';
260 34         153 $spec{coerce} = 1;
261             }
262             }
263              
264 209         1771 return \%spec;
265             }
266              
267             sub _parse_prototype_injections {
268 6     6   38834 my $self = shift;
269              
270 6         18 my @params;
271 6         27 for my $inject (@{ $self->prototype_injections }) {
  6         314  
272 6         10 my $param;
273 6         16 eval {
274 6         55 $param = Parse::Method::Signatures->param($inject);
275             };
276              
277 6 50 33     412622 confess "There was a problem parsing the prototype injection '$inject': $@"
278             if $@ || !defined $param;
279              
280 6         30 push @params, $param;
281             }
282              
283 6         23 my @return = reverse @params;
284 6         369 $self->_set_parsed_prototype_injections(\@return);
285             }
286              
287             sub _build__lexicals {
288 152     152   335 my ($self) = @_;
289 152         7760 my ($sig) = $self->parsed_signature;
290              
291 152         349 my @lexicals;
292              
293 152 100       9482 if ($self->_has_parsed_prototype_injections) {
294 3         160 push @lexicals, $_->variable_name
295 3         6 for @{ $self->_parsed_prototype_injections };
296             }
297              
298 152 100       7533 push @lexicals, $sig->has_invocant
299             ? $sig->invocant->variable_name
300             : '$self';
301              
302             push @lexicals,
303             (does_role($_, Placeholder)
304             ? 'undef'
305             : $_->variable_name)
306 152 100       3753 for (($sig->has_positional_params ? $sig->positional_params : ()),
    100          
    100          
307             ($sig->has_named_params ? $sig->named_params : ()));
308              
309 152         175774 return \@lexicals;
310             }
311              
312             sub _build_injectable_code {
313 152     152   312 my ($self) = @_;
314 152         302 my $vars = join q{,}, @{ $self->_lexicals };
  152         7657  
315 152         7882 return "my (${vars}) = \@_;";
316             }
317              
318             sub _build__positional_args {
319 158     158   469 my ($self) = @_;
320 158         7974 my $sig = $self->parsed_signature;
321              
322 157         385 my @positional;
323 157 100       10003 if ($self->_has_parsed_prototype_injections) {
324 3         14 push @positional, map {
325 3         166 $self->_param_to_spec($_)
326 3         7 } @{ $self->_parsed_prototype_injections };
327             }
328              
329 157 100       7191 push @positional, $sig->has_invocant
330             ? $self->_param_to_spec($sig->invocant)
331             : { tc => Object };
332              
333 157         28393 my $slurpy = 0;
334 157 100       952 if ($sig->has_positional_params) {
335 101         24476 for my $param ($sig->positional_params) {
336 126         10524 my $spec = $self->_param_to_spec($param);
337 125 100 50     714 $slurpy ||= 1 if $spec->{slurpy};
338 125         446 push @positional, $spec;
339             }
340             }
341              
342 156         12449 $self->_has_slurpy_positional($slurpy);
343 156         7590 return \@positional;
344             }
345              
346             sub _build__named_args {
347 156     156   329 my ($self) = @_;
348 156         13564 my $sig = $self->parsed_signature;
349              
350             # triggering building of positionals before named params is important
351             # because the latter needs to know if there have been any slurpy
352             # positionals to report errors
353 156         8210 $self->_positional_args;
354              
355 156         272 my @named;
356              
357 156 100       891 if ($sig->has_named_params) {
358 52 100       12344 confess 'Named parameters cannot be combined with slurpy positionals'
359             if $self->_has_slurpy_positional;
360 51         202 for my $param ($sig->named_params) {
361 61         7954 push @named, $param->label => $self->_param_to_spec($param);
362             }
363             }
364              
365 155         13452 return \@named;
366             }
367              
368             sub _build_type_constraint {
369 158     158   317 my ($self) = @_;
370 158         438 my ($positional, $named) = map { $self->$_ } map { "_${_}_args" } qw/positional named/;
  314         27653  
  316         1205  
371              
372 281         965 my $tc = Tuple[
373 155 100       361 Tuple[ map { $_->{tc} } @{ $positional } ],
  122         687  
374 155         512 Dict[ map { ref $_ ? $_->{tc} : $_ } @{ $named } ],
  155         950  
375             ];
376              
377             my $coerce_param = sub {
378 313     313   548 my ($spec, $value) = @_;
379 313 100       1513 return $value unless exists $spec->{coerce};
380 7         53 return $spec->{tc}->coerce($value);
381 155         2185006 };
382              
383 155         472 my %named = @{ $named };
  155         596  
384              
385             coerce $tc,
386             from ArrayRef,
387             via {
388 136     136   760995 my (@positional_args, %named_args);
389              
390 136         318 my $i = 0;
391 136         253 for my $param (@{ $positional }) {
  136         432  
392 260         687 push @positional_args, map { $coerce_param->($param, $_) }
  291         1772  
393 291 100       414 $#{ $_ } < $i
    100          
394             ? (exists $param->{default} ? eval $param->{default} : ())
395             : $_->[$i];
396 291         3600 $i++;
397             }
398              
399 136 100       481 if (%named) {
  89 100       360  
400 47         132 my @rest = @{ $_ }[$i .. $#{ $_ }];
  47         205  
  47         127  
401 47 100       796 confess "Expected named arguments but didn't find an even-sized list"
402             unless @rest % 2 == 0;
403 44         172 my %rest = @rest;
404              
405 44         254 while (my ($key, $spec) = each %named) {
406 68 100       201 if (exists $rest{$key}) {
407 48         159 $named_args{$key} = $coerce_param->($spec, delete $rest{$key});
408 48         1573 next;
409             }
410              
411 20 100       106 if (exists $spec->{default}) {
412 5         410 $named_args{$key} = $coerce_param->($spec, eval $spec->{default});
413             }
414             }
415              
416 44         178 @named_args{keys %rest} = values %rest;
417             }
418             elsif ($#{ $_ } >= $i) {
419 8         20 push @positional_args, @{ $_ }[$i .. $#{ $_ }];
  8         22  
  8         19  
420             }
421              
422 133         1154 return [\@positional_args, \%named_args];
423 155         780 };
424              
425 155         403707 return MooseX::Meta::TypeConstraint::ForceCoercion->new(
426             type_constraint => $tc,
427             );
428             }
429              
430             sub validate {
431 136     136 0 309 my ($self, $args) = @_;
432              
433 136         267 my @named = grep { !ref $_ } @{ $self->_named_args };
  142         411  
  136         7216  
434              
435 136         277 my $coerced;
436 136 100       6564 if (defined (my $msg = $self->type_constraint->validate($args, \$coerced))) {
437 43         271253 confess $msg;
438             }
439              
440 90         291385 return @{ $coerced->[0] }, map { $coerced->[1]->{$_} } @named;
  90         584  
  37         233  
441             }
442              
443             __PACKAGE__->meta->make_immutable;
444              
445              
446             1;
447              
448             __END__
449              
450             =pod
451              
452             =encoding UTF-8
453              
454             =for :stopwords Florian Ragwitz metaclass
455              
456             =head1 NAME
457              
458             MooseX::Method::Signatures::Meta::Method - Provides the metaclass for methods with signatures
459              
460             =head1 VERSION
461              
462             version 0.47
463              
464             =head1 AUTHOR
465              
466             Florian Ragwitz <rafl@debian.org>
467              
468             =head1 COPYRIGHT AND LICENSE
469              
470             This software is copyright (c) 2013 by Florian Ragwitz.
471              
472             This is free software; you can redistribute it and/or modify it under
473             the same terms as the Perl 5 programming language system itself.
474              
475             =cut