push the accessor inlining code back into the attribute
[gitmo/Moose.git] / lib / Moose / Meta / Method / Constructor.pm
1
2 package Moose::Meta::Method::Constructor;
3
4 use strict;
5 use warnings;
6
7 use Carp ();
8 use Scalar::Util 'blessed', 'weaken', 'looks_like_number', 'refaddr';
9 use Try::Tiny;
10
11 our $VERSION   = '1.19';
12 our $AUTHORITY = 'cpan:STEVAN';
13
14 use base 'Moose::Meta::Method',
15          'Class::MOP::Method::Constructor';
16
17 sub new {
18     my $class   = shift;
19     my %options = @_;
20
21     my $meta = $options{metaclass};
22
23     (ref $options{options} eq 'HASH')
24         || $class->throw_error("You must pass a hash of options", data => $options{options});
25
26     ($options{package_name} && $options{name})
27         || $class->throw_error("You must supply the package_name and name parameters $Class::MOP::Method::UPGRADE_ERROR_TEXT");
28
29     my $self = bless {
30         'body'          => undef,
31         'package_name'  => $options{package_name},
32         'name'          => $options{name},
33         'options'       => $options{options},
34         'associated_metaclass' => $meta,
35         '_expected_method_class' => $options{_expected_method_class} || 'Moose::Object',
36     } => $class;
37
38     # we don't want this creating
39     # a cycle in the code, if not
40     # needed
41     weaken($self->{'associated_metaclass'});
42
43     $self->_initialize_body;
44
45     return $self;
46 }
47
48 ## method
49
50 sub _initialize_body {
51     my $self = shift;
52     # TODO:
53     # the %options should also include a both
54     # a call 'initializer' and call 'SUPER::'
55     # options, which should cover approx 90%
56     # of the possible use cases (even if it
57     # requires some adaption on the part of
58     # the author, after all, nothing is free)
59
60     my @source = (
61         'sub {',
62             'my $_instance = shift;',
63             'my $class = Scalar::Util::blessed($_instance) || $_instance;',
64             'if ($class ne \'' . $self->associated_metaclass->name . '\') {',
65                 'return ' . $self->_generate_fallback_constructor('$class') . ';',
66             '}',
67             $self->_generate_params('$params', '$class'),
68             $self->_generate_instance('$instance', '$class'),
69             $self->_generate_slot_initializers,
70             $self->_generate_triggers,
71             $self->_generate_BUILDALL,
72             'return $instance;',
73         '}'
74     );
75     warn join("\n", @source) if $self->options->{debug};
76
77     # We need to check if the attribute ->can('type_constraint')
78     # since we may be trying to immutabilize a Moose meta class,
79     # which in turn has attributes which are Class::MOP::Attribute
80     # objects, rather than Moose::Meta::Attribute. And
81     # Class::MOP::Attribute attributes have no type constraints.
82     # However we need to make sure we leave an undef value there
83     # because the inlined code is using the index of the attributes
84     # to determine where to find the type constraint
85
86     my $attrs = $self->_attributes;
87
88     my @type_constraints = map {
89         $_->can('type_constraint') ? $_->type_constraint : undef
90     } @$attrs;
91
92     my @type_constraint_bodies = map {
93         defined $_ ? $_->_compiled_type_constraint : undef;
94     } @type_constraints;
95
96     my $defaults = [map { $_->default } @$attrs];
97
98     my $code = try {
99         $self->_compile_code(
100             source      => \@source,
101             environment => {
102                 '$meta'  => \$self,
103                 '$attrs' => \$attrs,
104                 '$defaults' => \$defaults,
105                 '@type_constraints' => \@type_constraints,
106                 '@type_constraint_bodies' => \@type_constraint_bodies,
107             },
108         );
109     }
110     catch {
111         my $source = join("\n", @source);
112         $self->throw_error(
113             "Could not eval the constructor :\n\n$source\n\nbecause :\n\n$_",
114             error => $_,
115             data  => $source,
116         );
117     };
118
119     $self->{'body'} = $code;
120 }
121
122 sub _generate_fallback_constructor {
123     my $self = shift;
124     my ($class_var) = @_;
125     return $class_var . '->Moose::Object::new(@_)'
126 }
127
128 sub _generate_params {
129     my $self = shift;
130     my ($var, $class_var) = @_;
131     return (
132         'my ' . $var . ' = ',
133         $self->_generate_BUILDARGS($class_var, '@_'),
134         ';',
135     );
136 }
137
138 sub _generate_instance {
139     my $self = shift;
140     my ($var, $class_var) = @_;
141     my $meta = $self->associated_metaclass;
142
143     return (
144         'my ' . $var . ' = ',
145         $meta->inline_create_instance($class_var) . ';',
146     );
147 }
148
149 sub _generate_slot_initializers {
150     my $self = shift;
151     return map { $self->_generate_slot_initializer($_) }
152                0 .. (@{$self->_attributes} - 1);
153 }
154
155 sub _generate_BUILDARGS {
156     my $self = shift;
157     my ($class, $args) = @_;
158
159     my $buildargs = $self->associated_metaclass->find_method_by_name("BUILDARGS");
160
161     if ($args eq '@_'
162      && (!$buildargs or $buildargs->body == \&Moose::Object::BUILDARGS)) {
163
164         return (
165             'do {',
166                 'my $params;',
167                 'if (scalar @_ == 1) {',
168                     'if (!defined($_[0]) || ref($_[0]) ne \'HASH\') {',
169                         $self->_inline_throw_error(
170                             '"Single parameters to new() must be a HASH ref"',
171                             'data => $_[0]',
172                         ) . ';',
173                     '}',
174                     '$params = { %{ $_[0] } };',
175                 '}',
176                 'elsif (@_ % 2) {',
177                     'Carp::carp(',
178                         '"The new() method for ' . $class . ' expects a '
179                       . 'hash reference or a key/value list. You passed an '
180                       . 'odd number of arguments"',
181                     ');',
182                     '$params = {@_, undef};',
183                 '}',
184                 'else {',
185                     '$params = {@_};',
186                 '}',
187                 '$params;',
188             '}',
189         );
190     }
191     else {
192         return $class . '->BUILDARGS(' . $args . ')';
193     }
194 }
195
196 sub _generate_BUILDALL {
197     my $self = shift;
198
199     my @methods = reverse $self->associated_metaclass->find_all_methods_by_name('BUILD');
200     my @BUILD_calls;
201
202     foreach my $method (@methods) {
203         push @BUILD_calls,
204             '$instance->' . $method->{class} . '::BUILD($params);';
205     }
206
207     return @BUILD_calls;
208 }
209
210 sub _generate_triggers {
211     my $self = shift;
212     my @trigger_calls;
213
214     for my $i (0 .. $#{ $self->_attributes }) {
215         my $attr = $self->_attributes->[$i];
216
217         next unless $attr->can('has_trigger') && $attr->has_trigger;
218
219         my $init_arg = $attr->init_arg;
220         next unless defined $init_arg;
221
222         push @trigger_calls,
223             'if (exists $params->{\'' . $init_arg . '\'}) {',
224                 '$attrs->[' . $i . ']->trigger->(',
225                     '$instance,',
226                     $attr->_inline_instance_get('$instance') . ',',
227                 ');',
228             '}';
229     }
230
231     return @trigger_calls;
232 }
233
234 sub _generate_slot_initializer {
235     my $self  = shift;
236     my ($index) = @_;
237
238     my $attr = $self->_attributes->[$index];
239
240     my @source = ('## ' . $attr->name);
241
242     push @source, $self->_check_required_attr($attr);
243
244     if (defined $attr->init_arg) {
245         push @source,
246             'if (exists $params->{\'' . $attr->init_arg . '\'}) {',
247                 $self->_init_attr_from_constructor($attr, $index),
248             '}';
249         if (my @default = $self->_init_attr_from_default($attr, $index)) {
250             push @source,
251                 'else {',
252                     @default,
253                 '}';
254         }
255     }
256     else {
257         if (my @default = $self->_init_attr_from_default($attr, $index)) {
258             push @source,
259                 '{', # _init_attr_from_default creates variables
260                     @default,
261                 '}';
262         }
263     }
264
265     return @source;
266 }
267
268 sub _check_required_attr {
269     my $self = shift;
270     my ($attr) = @_;
271
272     return unless defined $attr->init_arg;
273     return unless $attr->can('is_required') && $attr->is_required;
274     return if $attr->has_default || $attr->has_builder;
275
276     return (
277         'if (!exists $params->{\'' . $attr->init_arg . '\'}) {',
278             $self->_inline_throw_error(
279                 '"Attribute (' . quotemeta($attr->name) . ') is required"'
280             ) . ';',
281         '}',
282     );
283 }
284
285 sub _init_attr_from_constructor {
286     my $self = shift;
287     my ($attr, $index) = @_;
288
289     return (
290         'my $val = $params->{\'' . $attr->init_arg . '\'};',
291         $self->_generate_slot_assignment($attr, $index, '$val'),
292     );
293 }
294
295 sub _init_attr_from_default {
296     my $self = shift;
297     my ($attr, $index) = @_;
298
299     my $default = $self->_generate_default_value($attr, $index);
300     return unless $default;
301
302     return (
303         'my $val = ' . $default . ';',
304         $self->_generate_slot_assignment($attr, $index, '$val'),
305     );
306 }
307
308 sub _generate_slot_assignment {
309     my $self = shift;
310     my ($attr, $index, $value) = @_;
311
312     my @source;
313
314     if ($self->can('_generate_type_constraint_and_coercion')) {
315         push @source, $self->_generate_type_constraint_and_coercion(
316             $attr, $index, $value,
317         );
318     }
319
320     if ($attr->has_initializer) {
321         push @source, (
322             '$attrs->[' . $index . ']->set_initial_value(',
323                 '$instance' . ',',
324                 $value . ',',
325             ');'
326         );
327     }
328     else {
329         push @source, (
330             $attr->_inline_instance_set('$instance', $value) . ';',
331         );
332     }
333
334     return @source;
335 }
336
337 sub _generate_type_constraint_and_coercion {
338     my $self = shift;
339     my ($attr, $index, $value) = @_;
340
341     return unless $attr->can('has_type_constraint')
342                && $attr->has_type_constraint;
343
344     my @source;
345
346     if ($attr->should_coerce && $attr->type_constraint->has_coercion) {
347         push @source => $self->_generate_type_coercion(
348             '$type_constraints[' . $index . ']',
349             $value,
350             $value,
351         );
352     }
353
354     push @source => $self->_generate_type_constraint_check(
355         $attr,
356         '$type_constraint_bodies[' . $index . ']',
357         '$type_constraints[' . $index . ']',
358         $value,
359     );
360
361     return @source;
362 }
363
364 sub _generate_type_coercion {
365     my $self = shift;
366     my ($tc_obj, $value, $return_value) = @_;
367     return $return_value . ' = ' . $tc_obj . '->coerce(' . $value . ');';
368 }
369
370 sub _generate_type_constraint_check {
371     my $self = shift;
372     my ($attr, $tc_body, $tc_obj, $value) = @_;
373     return (
374         $self->_inline_throw_error(
375             '"Attribute (' . quotemeta($attr->name) . ') '
376           . 'does not pass the type constraint because: " . '
377           . $tc_obj . '->get_message(' . $value . ')'
378         ),
379         'unless ' .  $tc_body . '->(' . $value . ');'
380     );
381 }
382
383 1;
384
385 __END__
386
387 =pod
388
389 =head1 NAME
390
391 Moose::Meta::Method::Constructor - Method Meta Object for constructors
392
393 =head1 DESCRIPTION
394
395 This class is a subclass of L<Class::MOP::Method::Constructor> that
396 provides additional Moose-specific functionality
397
398 To understand this class, you should read the the
399 L<Class::MOP::Method::Constructor> documentation as well.
400
401 =head1 INHERITANCE
402
403 C<Moose::Meta::Method::Constructor> is a subclass of
404 L<Moose::Meta::Method> I<and> L<Class::MOP::Method::Constructor>.
405
406 =head1 BUGS
407
408 See L<Moose/BUGS> for details on reporting bugs.
409
410 =head1 AUTHORS
411
412 Stevan Little E<lt>stevan@iinteractive.comE<gt>
413
414 =head1 COPYRIGHT AND LICENSE
415
416 Copyright 2006-2010 by Infinity Interactive, Inc.
417
418 L<http://www.iinteractive.com>
419
420 This library is free software; you can redistribute it and/or modify
421 it under the same terms as Perl itself.
422
423 =cut
424