Commit | Line | Data |
5cf3dbcf |
1 | |
2 | package Moose::Meta::Method::Constructor; |
3 | |
4 | use strict; |
5 | use warnings; |
6 | |
0d922627 |
7 | use Carp (); |
0fa70d03 |
8 | use Scalar::Util 'blessed', 'weaken', 'looks_like_number', 'refaddr'; |
55c361dc |
9 | use Try::Tiny; |
5cf3dbcf |
10 | |
245478d5 |
11 | our $VERSION = '1.19'; |
5cf3dbcf |
12 | our $AUTHORITY = 'cpan:STEVAN'; |
13 | |
badb7e89 |
14 | use base 'Moose::Meta::Method', |
bc89e9b5 |
15 | 'Class::MOP::Method::Constructor'; |
5cf3dbcf |
16 | |
17 | sub 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 |
50 | sub _initialize_body { |
5cf3dbcf |
51 | my $self = shift; |
e247d17c |
52 | $self->{'body'} = $self->_generate_constructor_method_inline; |
53 | } |
54 | |
55 | sub _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 | |
88 | sub _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 |
128 | sub _generate_fallback_constructor { |
60019185 |
129 | my $self = shift; |
130 | my ($class_var) = @_; |
131 | return $class_var . '->Moose::Object::new(@_)' |
5117b888 |
132 | } |
133 | |
b905f0db |
134 | sub _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 | |
144 | sub _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 | |
155 | sub _generate_slot_initializers { |
60019185 |
156 | my $self = shift; |
157 | return map { $self->_generate_slot_initializer($_) } |
158 | 0 .. (@{$self->_attributes} - 1); |
b905f0db |
159 | } |
160 | |
e606ae5f |
161 | sub _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 | |
202 | sub _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 |
216 | sub _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 |
240 | sub _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 | |
274 | sub _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 | |
291 | sub _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 | |
301 | sub _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 | |
314 | sub _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 |
343 | sub _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 |
370 | sub _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 | |
376 | sub _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 |
389 | 1; |
390 | |
5cf3dbcf |
391 | __END__ |
392 | |
393 | =pod |
394 | |
7a5b07b3 |
395 | =head1 NAME |
5cf3dbcf |
396 | |
397 | Moose::Meta::Method::Constructor - Method Meta Object for constructors |
398 | |
5cf3dbcf |
399 | =head1 DESCRIPTION |
400 | |
cec39889 |
401 | This class is a subclass of L<Class::MOP::Method::Constructor> that |
cefc9e36 |
402 | provides additional Moose-specific functionality |
403 | |
404 | To understand this class, you should read the the |
cec39889 |
405 | L<Class::MOP::Method::Constructor> documentation as well. |
d44714be |
406 | |
bc89e9b5 |
407 | =head1 INHERITANCE |
408 | |
409 | C<Moose::Meta::Method::Constructor> is a subclass of |
410 | L<Moose::Meta::Method> I<and> L<Class::MOP::Method::Constructor>. |
411 | |
c5fc2c21 |
412 | =head1 BUGS |
413 | |
414 | See L<Moose/BUGS> for details on reporting bugs. |
415 | |
5cf3dbcf |
416 | =head1 AUTHORS |
417 | |
418 | Stevan Little E<lt>stevan@iinteractive.comE<gt> |
419 | |
420 | =head1 COPYRIGHT AND LICENSE |
421 | |
7e0492d3 |
422 | Copyright 2006-2010 by Infinity Interactive, Inc. |
5cf3dbcf |
423 | |
424 | L<http://www.iinteractive.com> |
425 | |
426 | This library is free software; you can redistribute it and/or modify |
7a5b07b3 |
427 | it under the same terms as Perl itself. |
5cf3dbcf |
428 | |
429 | =cut |
430 | |