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