doh, forgot to actually add the recipe itself
[gitmo/Moose.git] / lib / Moose / Meta / Method / Constructor.pm
CommitLineData
5cf3dbcf 1
2package Moose::Meta::Method::Constructor;
3
4use strict;
5use warnings;
6
5cf3dbcf 7use Scalar::Util 'blessed', 'weaken', 'looks_like_number';
8
56d7c745 9our $VERSION = '0.73';
5cf3dbcf 10our $AUTHORITY = 'cpan:STEVAN';
11
badb7e89 12use base 'Moose::Meta::Method',
bc89e9b5 13 'Class::MOP::Method::Constructor';
5cf3dbcf 14
15sub new {
16 my $class = shift;
17 my %options = @_;
7a5b07b3 18
3e504337 19 my $meta = $options{metaclass};
20
21 (ref $options{options} eq 'HASH')
a9538ac9 22 || $class->throw_error("You must pass a hash of options", data => $options{options});
7a5b07b3 23
1b2aea39 24 ($options{package_name} && $options{name})
a9538ac9 25 || $class->throw_error("You must supply the package_name and name parameters $Class::MOP::Method::UPGRADE_ERROR_TEXT");
1b2aea39 26
5cf3dbcf 27 my $self = bless {
28 # from our superclass
e606ae5f 29 'body' => undef,
30 'package_name' => $options{package_name},
31 'name' => $options{name},
5cf3dbcf 32 # specific to this subclass
e606ae5f 33 'options' => $options{options},
34 'meta_instance' => $meta->get_meta_instance,
35 'attributes' => [ $meta->compute_all_applicable_attributes ],
5cf3dbcf 36 # ...
e606ae5f 37 'associated_metaclass' => $meta,
5cf3dbcf 38 } => $class;
39
7a5b07b3 40 # we don't want this creating
41 # a cycle in the code, if not
5cf3dbcf 42 # needed
e606ae5f 43 weaken($self->{'associated_metaclass'});
5cf3dbcf 44
415e6f85 45 $self->initialize_body;
5cf3dbcf 46
7a5b07b3 47 return $self;
5cf3dbcf 48}
49
308e04fa 50sub can_be_inlined {
51 my $self = shift;
52 my $metaclass = $self->associated_metaclass;
53
5d826cfa 54 my $expected_class = $self->_expected_constructor_class;
55
12875d6e 56 # If any of our parents have been made immutable, we are okay to
e4c7477b 57 # inline our own new method. The assumption is that an inlined new
58 # method provided by a parent does not actually get used by
59 # children anyway.
60 for my $meta (
61 grep { $_->is_immutable }
62 map { ( ref $metaclass )->initialize($_) }
5d826cfa 63 grep { $_ ne $expected_class }
e4c7477b 64 $metaclass->linearized_isa
65 ) {
70695d9c 66 my $transformer = $meta->immutable_transformer;
12875d6e 67
5d826cfa 68 # This is actually a false positive if we're in a subclass of
69 # this class, _and_ the expected class is not overridden (but
70 # should be), and the real expected class is actually
71 # immutable itself (see Fey::Object::Table for an example of
72 # how this can happen). I'm not sure how to actually handle
73 # that case, since it's effectively a bug in the subclass (for
74 # not overriding _expected_constructor_class).
e4c7477b 75 return 1 if $transformer->inlined_constructor;
12875d6e 76 }
77
308e04fa 78 if ( my $constructor = $metaclass->find_method_by_name( $self->name ) ) {
e4c7477b 79 my $class = $self->associated_metaclass->name;
308e04fa 80
81 if ( $constructor->body != $expected_class->can('new') ) {
81766020 82 my $warning
83 = "Not inlining a constructor for $class since it is not"
37236439 84 . " inheriting the default $expected_class constructor\n"
85 . "If you are certain you don't need to inline your"
86 . " constructor, specify inline_constructor => 0 in your"
87 . " call to $class->meta->make_immutable\n";
308e04fa 88
81766020 89 $warning .= " (constructor has method modifiers which would be lost if it were inlined)\n"
90 if $constructor->isa('Class::MOP::Method::Wrapped');
91
92 warn $warning;
93
308e04fa 94 return 0;
95 }
96 else {
97 return 1;
98 }
99 }
100
101 # This would be a rather weird case where we have no constructor
102 # in the inheritance chain.
103 return 1;
104}
105
106# This is here so can_be_inlined can be inherited by MooseX modules.
107sub _expected_constructor_class {
108 return 'Moose::Object';
109}
110
7a5b07b3 111## accessors
5cf3dbcf 112
e606ae5f 113sub meta_instance { (shift)->{'meta_instance'} }
114sub attributes { (shift)->{'attributes'} }
5cf3dbcf 115
5cf3dbcf 116## method
117
415e6f85 118sub initialize_body {
5cf3dbcf 119 my $self = shift;
120 # TODO:
7a5b07b3 121 # the %options should also include a both
122 # a call 'initializer' and call 'SUPER::'
123 # options, which should cover approx 90%
124 # of the possible use cases (even if it
125 # requires some adaption on the part of
5cf3dbcf 126 # the author, after all, nothing is free)
127 my $source = 'sub {';
1f779926 128 $source .= "\n" . 'my $class = shift;';
7a5b07b3 129
587ae0d2 130 $source .= "\n" . 'return $class->Moose::Object::new(@_)';
ac070e13 131 $source .= "\n if \$class ne '" . $self->associated_metaclass->name
132 . "';\n";
93e98578 133
ac070e13 134 $source .= $self->_generate_params('$params', '$class');
135 $source .= $self->_generate_instance('$instance', '$class');
136 $source .= $self->_generate_slot_initializers;
7a5b07b3 137
ac070e13 138 $source .= $self->_generate_triggers();
5cf3dbcf 139 $source .= ";\n" . $self->_generate_BUILDALL();
7a5b07b3 140
ac070e13 141 $source .= ";\nreturn \$instance";
7a5b07b3 142 $source .= ";\n" . '}';
143 warn $source if $self->options->{debug};
144
5442a061 145 # We need to check if the attribute ->can('type_constraint')
146 # since we may be trying to immutabilize a Moose meta class,
147 # which in turn has attributes which are Class::MOP::Attribute
148 # objects, rather than Moose::Meta::Attribute. And
149 # Class::MOP::Attribute attributes have no type constraints.
150 # However we need to make sure we leave an undef value there
151 # because the inlined code is using the index of the attributes
152 # to determine where to find the type constraint
153
154 my $attrs = $self->attributes;
155
156 my @type_constraints = map {
157 $_->can('type_constraint') ? $_->type_constraint : undef
158 } @$attrs;
159
160 my @type_constraint_bodies = map {
161 defined $_ ? $_->_compiled_type_constraint : undef;
162 } @type_constraints;
163
164 my $code = $self->_compile_code(
165 code => $source,
166 environment => {
167 '$meta' => \$self,
168 '$attrs' => \$attrs,
169 '@type_constraints' => \@type_constraints,
170 '@type_constraint_bodies' => \@type_constraint_bodies,
171 },
172 ) or $self->throw_error("Could not eval the constructor :\n\n$source\n\nbecause :\n\n$@", error => $@, data => $source );
a62dcd43 173
e606ae5f 174 $self->{'body'} = $code;
175}
176
b905f0db 177sub _generate_params {
178 my ( $self, $var, $class_var ) = @_;
179 "my $var = " . $self->_generate_BUILDARGS( $class_var, '@_' ) . ";\n";
180}
181
182sub _generate_instance {
183 my ( $self, $var, $class_var ) = @_;
184 "my $var = "
185 . $self->meta_instance->inline_create_instance($class_var) . ";\n";
186}
187
188sub _generate_slot_initializers {
189 my ($self) = @_;
190 return (join ";\n" => map {
191 $self->_generate_slot_initializer($_)
192 } 0 .. (@{$self->attributes} - 1)) . ";\n";
193}
194
e606ae5f 195sub _generate_BUILDARGS {
196 my ( $self, $class, $args ) = @_;
197
198 my $buildargs = $self->associated_metaclass->find_method_by_name("BUILDARGS");
199
200 if ( $args eq '@_' and ( !$buildargs or $buildargs->body == \&Moose::Object::BUILDARGS ) ) {
201 return join("\n",
202 'do {',
203 $self->_inline_throw_error('"Single parameters to new() must be a HASH ref"', 'data => $_[0]'),
a62dcd43 204 ' if scalar @_ == 1 && !( defined $_[0] && ref $_[0] eq q{HASH} );',
e606ae5f 205 '(scalar @_ == 1) ? {%{$_[0]}} : {@_};',
206 '}',
207 );
208 } else {
209 return $class . "->BUILDARGS($args)";
210 }
5cf3dbcf 211}
212
213sub _generate_BUILDALL {
214 my $self = shift;
215 my @BUILD_calls;
1f779926 216 foreach my $method (reverse $self->associated_metaclass->find_all_methods_by_name('BUILD')) {
e606ae5f 217 push @BUILD_calls => '$instance->' . $method->{class} . '::BUILD($params)';
5cf3dbcf 218 }
7a5b07b3 219 return join ";\n" => @BUILD_calls;
5cf3dbcf 220}
221
1b55c340 222sub _generate_triggers {
223 my $self = shift;
224 my @trigger_calls;
708b4070 225 foreach my $i ( 0 .. $#{ $self->attributes } ) {
1b55c340 226 my $attr = $self->attributes->[$i];
708b4070 227
228 next unless $attr->can('has_trigger') && $attr->has_trigger;
229
230 my $init_arg = $attr->init_arg;
231
232 next unless defined $init_arg;
233
234 push @trigger_calls => '(exists $params->{\''
235 . $init_arg
236 . '\'}) && do {'
237 . "\n "
238 . '$attrs->['
239 . $i
240 . ']->trigger->('
241 . '$instance, '
242 . $self->meta_instance->inline_get_slot_value(
243 '$instance',
244 $attr->name,
245 )
246 . ', '
c2685d20 247 . ');' . "\n}";
1b55c340 248 }
708b4070 249
250 return join ";\n" => @trigger_calls;
1b55c340 251}
252
5cf3dbcf 253sub _generate_slot_initializer {
254 my $self = shift;
255 my $index = shift;
7a5b07b3 256
5cf3dbcf 257 my $attr = $self->attributes->[$index];
7a5b07b3 258
5cf3dbcf 259 my @source = ('## ' . $attr->name);
d66bea3c 260
261 my $is_moose = $attr->isa('Moose::Meta::Attribute'); # XXX FIXME
7a5b07b3 262
84981146 263 if ($is_moose && defined($attr->init_arg) && $attr->is_required && !$attr->has_default && !$attr->has_builder) {
e606ae5f 264 push @source => ('(exists $params->{\'' . $attr->init_arg . '\'}) ' .
3e504337 265 '|| ' . $self->_inline_throw_error('"Attribute (' . $attr->name . ') is required"') .';');
5cf3dbcf 266 }
7a5b07b3 267
ca168e89 268 if (($attr->has_default || $attr->has_builder) && !($is_moose && $attr->is_lazy)) {
7a5b07b3 269
84981146 270 if ( defined( my $init_arg = $attr->init_arg ) ) {
e606ae5f 271 push @source => 'if (exists $params->{\'' . $init_arg . '\'}) {';
272 push @source => ('my $val = $params->{\'' . $init_arg . '\'};');
273 push @source => $self->_generate_type_constraint_and_coercion($attr, $index)
274 if $is_moose;
275 push @source => $self->_generate_slot_assignment($attr, '$val', $index);
84981146 276 push @source => "} else {";
277 }
ca168e89 278 my $default;
97e11ef5 279 if ( $attr->has_default ) {
ca168e89 280 $default = $self->_generate_default_value($attr, $index);
97e11ef5 281 }
282 else {
ca168e89 283 my $builder = $attr->builder;
284 $default = '$instance->' . $builder;
285 }
688fcdda 286
3db3ea82 287 push @source => '{'; # wrap this to avoid my $val overwrite warnings
5cf3dbcf 288 push @source => ('my $val = ' . $default . ';');
e606ae5f 289 push @source => $self->_generate_type_constraint_and_coercion($attr, $index)
290 if $is_moose;
51c107ef 291 push @source => $self->_generate_slot_assignment($attr, '$val', $index);
bad76b8e 292 push @source => '}'; # close - wrap this to avoid my $val overrite warnings
7a5b07b3 293
84981146 294 push @source => "}" if defined $attr->init_arg;
7a5b07b3 295 }
84981146 296 elsif ( defined( my $init_arg = $attr->init_arg ) ) {
e606ae5f 297 push @source => '(exists $params->{\'' . $init_arg . '\'}) && do {';
8ecb1fa0 298
e606ae5f 299 push @source => ('my $val = $params->{\'' . $init_arg . '\'};');
d66bea3c 300 if ($is_moose && $attr->has_type_constraint) {
7a5b07b3 301 if ($attr->should_coerce && $attr->type_constraint->has_coercion) {
688fcdda 302 push @source => $self->_generate_type_coercion(
303 $attr,
304 '$type_constraints[' . $index . ']',
305 '$val',
306 '$val'
307 );
8ecb1fa0 308 }
688fcdda 309 push @source => $self->_generate_type_constraint_check(
310 $attr,
311 '$type_constraint_bodies[' . $index . ']',
312 '$type_constraints[' . $index . ']',
313 '$val'
314 );
8ecb1fa0 315 }
9df136d0 316 push @source => $self->_generate_slot_assignment($attr, '$val', $index);
7a5b07b3 317
318 push @source => "}";
5cf3dbcf 319 }
7a5b07b3 320
5cf3dbcf 321 return join "\n" => @source;
322}
323
324sub _generate_slot_assignment {
9df136d0 325 my ($self, $attr, $value, $index) = @_;
326
327 my $source;
328
329 if ($attr->has_initializer) {
330 $source = (
331 '$attrs->[' . $index . ']->set_initial_value($instance, ' . $value . ');'
332 );
333 }
334 else {
335 $source = (
336 $self->meta_instance->inline_set_slot_value(
337 '$instance',
eae37c67 338 $attr->name,
9df136d0 339 $value
340 ) . ';'
341 );
342 }
343
344 my $is_moose = $attr->isa('Moose::Meta::Attribute'); # XXX FIXME
7a5b07b3 345
d66bea3c 346 if ($is_moose && $attr->is_weak_ref) {
5cf3dbcf 347 $source .= (
348 "\n" .
349 $self->meta_instance->inline_weaken_slot_value(
7a5b07b3 350 '$instance',
eae37c67 351 $attr->name
7a5b07b3 352 ) .
5cf3dbcf 353 ' if ref ' . $value . ';'
7a5b07b3 354 );
355 }
356
5cf3dbcf 357 return $source;
358}
359
e606ae5f 360sub _generate_type_constraint_and_coercion {
361 my ($self, $attr, $index) = @_;
362
363 return unless $attr->has_type_constraint;
364
365 my @source;
366 if ($attr->should_coerce && $attr->type_constraint->has_coercion) {
367 push @source => $self->_generate_type_coercion(
368 $attr,
369 '$type_constraints[' . $index . ']',
370 '$val',
371 '$val'
372 );
373 }
374 push @source => $self->_generate_type_constraint_check(
375 $attr,
376 ('$type_constraint_bodies[' . $index . ']'),
377 ('$type_constraints[' . $index . ']'),
378 '$val'
379 );
380 return @source;
381}
382
5cf3dbcf 383sub _generate_type_coercion {
384 my ($self, $attr, $type_constraint_name, $value_name, $return_value_name) = @_;
385 return ($return_value_name . ' = ' . $type_constraint_name . '->coerce(' . $value_name . ');');
386}
387
388sub _generate_type_constraint_check {
688fcdda 389 my ($self, $attr, $type_constraint_cv, $type_constraint_obj, $value_name) = @_;
5cf3dbcf 390 return (
3e504337 391 $self->_inline_throw_error('"Attribute (' # FIXME add 'dad'
688fcdda 392 . $attr->name
393 . ') does not pass the type constraint because: " . '
3e504337 394 . $type_constraint_obj . '->get_message(' . $value_name . ')')
395 . "\n\t unless " . $type_constraint_cv . '->(' . $value_name . ');'
7a5b07b3 396 );
5cf3dbcf 397}
398
399sub _generate_default_value {
400 my ($self, $attr, $index) = @_;
401 # NOTE:
402 # default values can either be CODE refs
7a5b07b3 403 # in which case we need to call them. Or
5cf3dbcf 404 # they can be scalars (strings/numbers)
405 # in which case we can just deal with them
406 # in the code we eval.
407 if ($attr->is_default_a_coderef) {
408 return '$attrs->[' . $index . ']->default($instance)';
409 }
410 else {
4aa3d405 411 return q{"} . quotemeta( $attr->default ) . q{"};
7a5b07b3 412 }
5cf3dbcf 413}
414
4151;
416
5cf3dbcf 417__END__
418
419=pod
420
7a5b07b3 421=head1 NAME
5cf3dbcf 422
423Moose::Meta::Method::Constructor - Method Meta Object for constructors
424
5cf3dbcf 425=head1 DESCRIPTION
426
cefc9e36 427This class is a subclass of L<Class::MOP::Class::Constructor> that
428provides additional Moose-specific functionality
429
430To understand this class, you should read the the
431L<Class::MOP::Class::Constructor> documentation as well.
d44714be 432
bc89e9b5 433=head1 INHERITANCE
434
435C<Moose::Meta::Method::Constructor> is a subclass of
436L<Moose::Meta::Method> I<and> L<Class::MOP::Method::Constructor>.
437
5cf3dbcf 438=head1 METHODS
439
440=over 4
441
cefc9e36 442=item B<< $metamethod->can_be_inlined >>
a1257460 443
cefc9e36 444This returns true if the method can inlined.
5cf3dbcf 445
cefc9e36 446First, it looks at all of the parents of the associated class. If any
447of them have an inlined constructor, then the constructor can be
448inlined.
5cf3dbcf 449
cefc9e36 450If none of them have been inlined, it checks to make sure that the
451pre-inlining constructor for the class matches the constructor from
452the expected class.
5cf3dbcf 453
cefc9e36 454By default, it expects this constructor come from L<Moose::Object>,
455but subclasses can change this expectation.
5cf3dbcf 456
cefc9e36 457If the constructor cannot be inlined it warns that this is the case.
5cf3dbcf 458
459=back
460
461=head1 AUTHORS
462
463Stevan Little E<lt>stevan@iinteractive.comE<gt>
464
465=head1 COPYRIGHT AND LICENSE
466
2840a3b2 467Copyright 2006-2009 by Infinity Interactive, Inc.
5cf3dbcf 468
469L<http://www.iinteractive.com>
470
471This library is free software; you can redistribute it and/or modify
7a5b07b3 472it under the same terms as Perl itself.
5cf3dbcf 473
474=cut
475