1 package MooseX::Method::Signatures::Meta::Method;
6 use Parse::Method::Signatures;
7 use Parse::Method::Signatures::TypeConstraint;
8 use Scalar::Util qw/weaken/;
9 use Moose::Util qw/does_role/;
10 use Moose::Util::TypeConstraints;
11 use MooseX::Meta::TypeConstraint::ForceCoercion;
12 use MooseX::Types::Util qw/has_available_type_export/;
13 use MooseX::Types::Structured qw/Tuple Dict Optional slurpy/;
14 use MooseX::Types::Moose qw/ArrayRef Str Maybe Object Any CodeRef Bool/;
15 use MooseX::Method::Signatures::Types qw/Injections Params/;
16 use aliased 'Parse::Method::Signatures::Param::Named';
17 use aliased 'Parse::Method::Signatures::Param::Placeholder';
19 use namespace::autoclean;
21 extends 'Moose::Meta::Method';
30 has parsed_signature => (
32 isa => class_type('Parse::Method::Signatures::Sig'),
34 builder => '_build_parsed_signature',
37 sub _parsed_signature {
38 cluck '->_parsed_signature is deprecated. use ->parsed_signature instead.';
39 shift->parsed_signature;
46 builder => '_build__lexicals',
49 has injectable_code => (
53 builder => '_build_injectable_code',
56 has _positional_args => (
60 builder => '_build__positional_args',
67 builder => '_build__named_args',
70 has _has_slurpy_positional => (
75 has type_constraint => (
77 isa => class_type('Moose::Meta::TypeConstraint'),
79 builder => '_build_type_constraint',
82 has return_signature => (
85 predicate => 'has_return_signature',
88 has _return_type_constraint => (
90 isa => class_type('Moose::Meta::TypeConstraint'),
92 builder => '_build__return_type_constraint',
98 predicate => '_has_actual_body',
101 has prototype_injections => (
104 trigger => \&_parse_prototype_injections
107 has _parsed_prototype_injections => (
110 predicate => '_has_parsed_prototype_injections',
111 writer => '_set_parsed_prototype_injections',
114 before actual_body => sub {
116 confess "method doesn't have an actual body yet"
117 unless $self->_has_actual_body;
121 my ($next, $self) = @_;
122 my $ret = $self->$next;
123 confess "method doesn't have a name yet"
129 my ($class, $self, %args) = @_;
131 if (exists $args{return_signature}) {
133 my @args = ${ $self }->validate(\@_);
134 return preserve_context { ${ $self }->actual_body->(@args) }
136 if (defined (my $msg = ${ $self }->_return_type_constraint->validate(\@_))) {
145 @_ = ${ $self }->validate(\@_);
146 $actual_body ||= ${ $self }->actual_body;
147 goto &{ $actual_body };
153 my ($class, %args) = @_;
155 $args{actual_body} = delete $args{body}
156 if exists $args{body};
159 my $to_wrap = $class->_wrapped_body(\$self, %args);
164 Class::MOP::load_class($_->[0]); $_->[0];
165 } @{ $args{traits} };
167 my $meta = Moose::Meta::Class->create_anon_class(
168 superclasses => [ $class ],
169 roles => [ @traits ],
172 $meta->add_method(meta => sub { $meta });
174 $class = $meta->name;
177 $self = $class->_new(%args, body => $to_wrap);
179 # Vivify the type constraints so TC lookups happen before namespace::clean
181 $self->type_constraint;
182 $self->_return_type_constraint if $self->has_return_signature;
184 weaken($self->{associated_metaclass})
185 if $self->{associated_metaclass};
191 my ($self, %params) = @_;
192 my $trait_args = delete $params{trait_args};
195 $clone = $self->meta->clone_object($self,
196 %params, @{ $trait_args || [] },
197 body => $self->_wrapped_body(\$clone,
198 ($self->has_return_signature
199 ? (return_signature => $self->return_signature)
207 sub _build_parsed_signature {
209 return Parse::Method::Signatures->signature(
210 input => $self->signature,
211 from_namespace => $self->package_name,
215 sub _build__return_type_constraint {
217 confess 'no return type constraint'
218 unless $self->has_return_signature;
220 my $parser = Parse::Method::Signatures->new(
221 input => $self->return_signature,
222 from_namespace => $self->package_name,
225 my $param = $parser->_param_typed({});
226 confess 'failed to parse return value type constraint'
227 unless exists $param->{type_constraints};
229 return Tuple[$param->{type_constraints}->tc];
233 my ($self, $param) = @_;
237 # Ensure errors get reported from the right place
238 local $Carp::Internal{'MooseX::Method::Signatures::Meta::Method'} = 1;
239 local $Carp::Internal{'Moose::Meta::Method::Delegation'} = 1;
240 local $Carp::Internal{'Moose::Meta::Method::Accessor'} = 1;
241 local $Carp::Internal{'MooseX::Method::Signatures'} = 1;
242 local $Carp::Internal{'Devel::Declare'} = 1;
243 $tc = $param->meta_type_constraint
244 if $param->has_type_constraints;
247 if ($param->has_constraints) {
248 my $cb = join ' && ', map { "sub {${_}}->(\\\@_)" } $param->constraints;
249 my $code = eval "sub {${cb}}";
250 $tc = subtype($tc, $code);
254 if ($param->sigil ne '$') {
256 $tc = slurpy ArrayRef[$tc];
259 $spec{tc} = $param->required
263 $spec{default} = $param->default_value
264 if $param->has_default_value;
266 if ($param->has_traits) {
267 for my $trait (@{ $param->param_traits }) {
268 next unless $trait->[1] eq 'coerce';
276 sub _parse_prototype_injections {
280 for my $inject (@{ $self->prototype_injections }) {
283 $param = Parse::Method::Signatures->param($inject);
286 confess "There was a problem parsing the prototype injection '$inject': $@"
287 if $@ || !defined $param;
289 push @params, $param;
292 my @return = reverse @params;
293 $self->_set_parsed_prototype_injections(\@return);
296 sub _build__lexicals {
298 my ($sig) = $self->parsed_signature;
302 if ($self->_has_parsed_prototype_injections) {
303 push @lexicals, $_->variable_name
304 for @{ $self->_parsed_prototype_injections };
307 push @lexicals, $sig->has_invocant
308 ? $sig->invocant->variable_name
312 (does_role($_, Placeholder)
315 for (($sig->has_positional_params ? $sig->positional_params : ()),
316 ($sig->has_named_params ? $sig->named_params : ()));
321 sub _build_injectable_code {
323 my $vars = join q{,}, @{ $self->_lexicals };
324 return "my (${vars}) = \@_;";
327 sub _build__positional_args {
329 my $sig = $self->parsed_signature;
332 if ($self->_has_parsed_prototype_injections) {
333 push @positional, map {
334 $self->_param_to_spec($_)
335 } @{ $self->_parsed_prototype_injections };
338 push @positional, $sig->has_invocant
339 ? $self->_param_to_spec($sig->invocant)
343 if ($sig->has_positional_params) {
344 for my $param ($sig->positional_params) {
345 my $spec = $self->_param_to_spec($param);
346 $slurpy ||= 1 if $spec->{slurpy};
347 push @positional, $spec;
351 $self->_has_slurpy_positional($slurpy);
355 sub _build__named_args {
357 my $sig = $self->parsed_signature;
359 # triggering building of positionals before named params is important
360 # because the latter needs to know if there have been any slurpy
361 # positionals to report errors
362 $self->_positional_args;
366 if ($sig->has_named_params) {
367 confess 'Named parameters can not be combined with slurpy positionals'
368 if $self->_has_slurpy_positional;
369 for my $param ($sig->named_params) {
370 push @named, $param->label => $self->_param_to_spec($param);
377 sub _build_type_constraint {
379 my ($positional, $named) = map { $self->$_ } map { "_${_}_args" } qw/positional named/;
382 Tuple[ map { $_->{tc} } @{ $positional } ],
383 Dict[ map { ref $_ ? $_->{tc} : $_ } @{ $named } ],
386 my $coerce_param = sub {
387 my ($spec, $value) = @_;
388 return $value unless exists $spec->{coerce};
389 return $spec->{tc}->coerce($value);
392 my %named = @{ $named };
397 my (@positional_args, %named_args);
400 for my $param (@{ $positional }) {
401 push @positional_args,
403 ? (exists $param->{default} ? eval $param->{default} : ())
404 : $coerce_param->($param, $_->[$i]);
409 my %rest = @{ $_ }[$i .. $#{ $_ }];
410 while (my ($key, $spec) = each %named) {
411 if (exists $rest{$key}) {
412 $named_args{$key} = $coerce_param->($spec, delete $rest{$key});
416 if (exists $spec->{default}) {
417 $named_args{$key} = eval $spec->{default};
421 @named_args{keys %rest} = values %rest;
423 elsif ($#{ $_ } >= $i) {
424 push @positional_args, @{ $_ }[$i .. $#{ $_ }];
427 return [\@positional_args, \%named_args];
430 return MooseX::Meta::TypeConstraint::ForceCoercion->new(
431 type_constraint => $tc,
436 my ($self, $args) = @_;
438 my @named = grep { !ref $_ } @{ $self->_named_args };
441 if (defined (my $msg = $self->type_constraint->validate($args, \$coerced))) {
445 return @{ $coerced->[0] }, map { $coerced->[1]->{$_} } @named;
448 __PACKAGE__->meta->make_immutable;