make native trait inlining work
[gitmo/Moose.git] / lib / Moose / Meta / Method / Accessor.pm
1
2 package Moose::Meta::Method::Accessor;
3
4 use strict;
5 use warnings;
6
7 use Try::Tiny;
8
9 our $VERSION   = '1.19';
10 $VERSION = eval $VERSION;
11 our $AUTHORITY = 'cpan:STEVAN';
12
13 use base 'Moose::Meta::Method',
14          'Class::MOP::Method::Accessor';
15
16 sub _error_thrower {
17     my $self = shift;
18     return $self->associated_attribute
19         if ref($self) && defined($self->associated_attribute);
20     return $self->SUPER::_error_thrower;
21 }
22
23 sub _compile_code {
24     my $self = shift;
25     my @args = @_;
26     try {
27         $self->SUPER::_compile_code(@args);
28     }
29     catch {
30         $self->throw_error(
31             'Could not create writer for '
32           . "'" . $self->associated_attribute->name . "' "
33           . 'because ' . $_,
34             error => $_,
35         );
36     };
37 }
38
39 sub _eval_environment {
40     my $self = shift;
41
42     my $attr                = $self->associated_attribute;
43     my $type_constraint_obj = $attr->type_constraint;
44
45     return {
46         '$attr'                => \$attr,
47         '$meta'                => \$self,
48         '$type_constraint_obj' => \$type_constraint_obj,
49         '$type_constraint'     => \(
50               $type_constraint_obj
51                   ? $type_constraint_obj->_compiled_type_constraint
52                   : undef
53         ),
54     };
55 }
56
57 sub _generate_accessor_method_inline {
58     my $self        = shift;
59
60     my $inv         = '$_[0]';
61     my $slot_access = $self->_inline_get($inv);
62     my $value       = $self->_value_needs_copy ? '$val' : '$_[1]';
63     my $old         = '@old';
64
65     $self->_compile_code([
66         'sub {',
67             $self->_inline_pre_body(@_),
68             'if (scalar(@_) >= 2) {',
69                 $self->_inline_copy_value($value),
70                 $self->_inline_check_required,
71                 $self->_inline_tc_code($value),
72                 $self->_inline_get_old_value_for_trigger($inv, $old),
73                 $self->_inline_store($inv, $value),
74                 $self->_inline_trigger($inv, $value, $old),
75             '}',
76             $self->_inline_check_lazy($inv),
77             $self->_inline_post_body(@_),
78             'return ' . $self->_inline_auto_deref($slot_access) . ';',
79         '}',
80     ]);
81 }
82
83 sub _generate_writer_method_inline {
84     my $self        = shift;
85
86     my $inv   = '$_[0]';
87     my $value = $self->_value_needs_copy ? '$val' : '$_[1]';
88     my $old   = '@old';
89
90     $self->_compile_code([
91         'sub {',
92             $self->_inline_pre_body(@_),
93             $self->_inline_copy_value($value),
94             $self->_inline_check_required,
95             $self->_inline_tc_code($value),
96             $self->_inline_get_old_value_for_trigger($inv, $old),
97             $self->_inline_store($inv, $value),
98             $self->_inline_post_body(@_),
99             $self->_inline_trigger($inv, $value, $old),
100         '}',
101     ]);
102 }
103
104 sub _generate_reader_method_inline {
105     my $self        = shift;
106
107     my $inv         = '$_[0]';
108     my $slot_access = $self->_inline_get($inv);
109
110     $self->_compile_code([
111         'sub {',
112             $self->_inline_pre_body(@_),
113             'if (@_ > 1) {',
114                 $self->_inline_throw_error(
115                     '"Cannot assign a value to a read-only accessor"',
116                     'data => \@_'
117                 ) . ';',
118             '}',
119             $self->_inline_check_lazy($inv),
120             $self->_inline_post_body(@_),
121             'return ' . $self->_inline_auto_deref($slot_access) . ';',
122         '}',
123     ]);
124 }
125
126 sub _inline_copy_value {
127     my $self = shift;
128     my ($value) = @_;
129
130     return '' unless $self->_value_needs_copy;
131     return 'my ' . $value . ' = $_[1];'
132 }
133
134 sub _value_needs_copy {
135     my $self = shift;
136     return $self->associated_attribute->should_coerce;
137 }
138
139 sub _instance_is_inlinable {
140     my $self = shift;
141     return $self->associated_attribute->associated_class->instance_metaclass->is_inlinable;
142 }
143
144 sub _generate_reader_method {
145     my $self = shift;
146     $self->_instance_is_inlinable ? $self->_generate_reader_method_inline(@_)
147                                   : $self->SUPER::_generate_reader_method(@_);
148 }
149
150 sub _generate_writer_method {
151     my $self = shift;
152     $self->_instance_is_inlinable ? $self->_generate_writer_method_inline(@_)
153                                   : $self->SUPER::_generate_writer_method(@_);
154 }
155
156 sub _generate_accessor_method {
157     my $self = shift;
158     $self->_instance_is_inlinable ? $self->_generate_accessor_method_inline(@_)
159                                   : $self->SUPER::_generate_accessor_method(@_);
160 }
161
162 sub _generate_predicate_method {
163     my $self = shift;
164     $self->_instance_is_inlinable ? $self->_generate_predicate_method_inline(@_)
165                                   : $self->SUPER::_generate_predicate_method(@_);
166 }
167
168 sub _generate_clearer_method {
169     my $self = shift;
170     $self->_instance_is_inlinable ? $self->_generate_clearer_method_inline(@_)
171                                   : $self->SUPER::_generate_clearer_method(@_);
172 }
173
174 sub _inline_pre_body  { return }
175 sub _inline_post_body { return }
176
177 sub _inline_check_constraint {
178     my $self = shift;
179     my ($value) = @_;
180
181     my $attr = $self->associated_attribute;
182     return '' unless $attr->has_type_constraint;
183
184     my $attr_name = quotemeta( $attr->name );
185
186     return 'if (!$type_constraint->(' . $value . ')) {',
187                $self->_inline_throw_error(
188                    '"Attribute (' . $attr_name . ') does not pass the type '
189                  . 'constraint because: " . '
190                  . '$type_constraint_obj->get_message(' . $value . ')',
191                    'data => ' . $value
192                ) . ';',
193            '}';
194 }
195
196 sub _inline_tc_code {
197     my $self = shift;
198     return (
199         $self->_inline_check_coercion(@_),
200         $self->_inline_check_constraint(@_),
201     );
202 }
203
204 sub _inline_check_coercion {
205     my $self = shift;
206     my ($value) = @_;
207
208     my $attr = $self->associated_attribute;
209     return '' unless $attr->should_coerce
210                   && $attr->type_constraint->has_coercion;
211
212     return $value . ' = $type_constraint_obj->coerce(' . $value . ');';
213 }
214
215 sub _inline_check_required {
216     my $self = shift;
217
218     my $attr = $self->associated_attribute;
219     return '' unless $attr->is_required;
220
221     my $attr_name = quotemeta( $attr->name );
222
223     return 'if (@_ < 2) {',
224                $self->_inline_throw_error(
225                    '"Attribute (' . $attr_name . ') is required, so cannot '
226                  . 'be set to undef"' # defined $_[1] is not good enough
227                ) . ';',
228            '}';
229 }
230
231 sub _inline_check_lazy {
232     my $self = shift;
233     my ($instance, $default) = @_;
234
235     my $attr = $self->associated_attribute;
236     return unless $attr->is_lazy;
237
238     my $slot_exists = $self->_inline_has($instance);
239
240     return 'if (!' . $slot_exists . ') {',
241                $self->_inline_init_from_default($instance, '$default', 'lazy'),
242            '}';
243 }
244
245 sub _inline_init_from_default {
246     my $self = shift;
247     my ($instance, $default, $for_lazy) = @_;
248
249     my $attr = $self->associated_attribute;
250     # XXX: should this throw an error instead?
251     return $self->_inline_init_slot($attr, $instance, 'undef')
252         unless $attr->has_default || $attr->has_builder;
253
254     return $self->_inline_generate_default($instance, $default),
255            $attr->has_type_constraint
256                  # intentionally not using _inline_tc_code, since that can be
257                  # overridden to do things like possibly only do member tc
258                  # checks, which isn't appropriate for checking the result
259                  # of a default
260                ? ($self->_inline_check_coercion($default, $for_lazy),
261                   $self->_inline_check_constraint($default, $for_lazy))
262                : (),
263            $self->_inline_init_slot($attr, $instance, $default);
264 }
265
266 sub _inline_generate_default {
267     my $self = shift;
268     my ($instance, $default) = @_;
269
270     my $attr = $self->associated_attribute;
271
272     if ($attr->has_default) {
273         return 'my ' . $default . ' = $attr->default(' . $instance . ');';
274     }
275     elsif ($attr->has_builder) {
276         return 'my ' . $default . ';',
277                'if (my $builder = ' . $instance . '->can($attr->builder)) {',
278                    $default . ' = ' . $instance . '->$builder;',
279                '}',
280                'else {',
281                    'my $class = ref(' . $instance . ') || ' . $instance . ';',
282                    'my $builder_name = $attr->builder;',
283                    'my $attr_name = $attr->name;',
284                    $self->_inline_throw_error(
285                        '"$class does not support builder method '
286                      . '\'$builder_name\' for attribute \'$attr_name\'"'
287                    ) . ';',
288                '}';
289     }
290     else {
291         $self->throw_error("Can't generate a default for " . $attr->name
292                          . " since no default or builder was specified");
293     }
294 }
295
296 sub _inline_init_slot {
297     my $self = shift;
298     my ($attr, $inv, $value) = @_;
299
300     if ($attr->has_initializer) {
301         return '$attr->set_initial_value(' . $inv . ', ' . $value . ');';
302     }
303     else {
304         return $self->_inline_store($inv, $value) . ';';
305     }
306 }
307
308 sub _inline_store {
309     my $self = shift;
310     my ($instance, $value) = @_;
311
312     return $self->associated_attribute->inline_set( $instance, $value ) . ';';
313 }
314
315 sub _inline_get_old_value_for_trigger {
316     my $self = shift;
317     my ($instance, $old) = @_;
318
319     my $attr = $self->associated_attribute;
320     return '' unless $attr->has_trigger;
321
322     return 'my ' . $old . ' = ' . $self->_inline_has($instance)
323              . ' ? ' . $self->_inline_get($instance)
324              . ' : ();';
325 }
326
327 sub _inline_trigger {
328     my $self = shift;
329     my ($instance, $value, $old) = @_;
330
331     my $attr = $self->associated_attribute;
332     return '' unless $attr->has_trigger;
333
334     return sprintf('$attr->trigger->(%s, %s, %s);', $instance, $value, $old);
335 }
336
337 # expressions
338
339 sub _inline_get {
340     my ($self, $instance) = @_;
341
342     return $self->associated_attribute->inline_get($instance);
343 }
344
345 sub _inline_has {
346     my ($self, $instance) = @_;
347
348     return $self->associated_attribute->inline_has($instance);
349 }
350
351 sub _inline_auto_deref {
352     my $self = shift;
353     my ($ref_value) = @_;
354
355     my $attr = $self->associated_attribute;
356     return $ref_value unless $attr->should_auto_deref;
357
358     my $type_constraint = $attr->type_constraint;
359
360     my $sigil;
361     if ($type_constraint->is_a_type_of('ArrayRef')) {
362         $sigil = '@';
363     }
364     elsif ($type_constraint->is_a_type_of('HashRef')) {
365         $sigil = '%';
366     }
367     else {
368         $self->throw_error(
369             "Can not auto de-reference the type constraint '"
370           . $type_constraint->name
371           . "'",
372             type_constraint => $type_constraint,
373         );
374     }
375
376     "(wantarray() ? $sigil\{ ( $ref_value ) || return } : ( $ref_value ) )";
377 }
378
379 1;
380
381 __END__
382
383 =pod
384
385 =head1 NAME
386
387 Moose::Meta::Method::Accessor - A Moose Method metaclass for accessors
388
389 =head1 DESCRIPTION
390
391 This class is a subclass of L<Class::MOP::Method::Accessor> that
392 provides additional Moose-specific functionality, all of which is
393 private.
394
395 To understand this class, you should read the the
396 L<Class::MOP::Method::Accessor> documentation.
397
398 =head1 BUGS
399
400 See L<Moose/BUGS> for details on reporting bugs.
401
402 =head1 AUTHOR
403
404 Stevan Little E<lt>stevan@iinteractive.comE<gt>
405
406 Yuval Kogman E<lt>nothingmuch@woobling.comE<gt>
407
408 =head1 COPYRIGHT AND LICENSE
409
410 Copyright 2006-2010 by Infinity Interactive, Inc.
411
412 L<http://www.iinteractive.com>
413
414 This library is free software; you can redistribute it and/or modify
415 it under the same terms as Perl itself.
416
417 =cut