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