6b387da4609f5b42ad8d9a890e93154c8f57ac9c
[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 Carp 'confess';
8
9 our $VERSION   = '0.06';
10 our $AUTHORITY = 'cpan:STEVAN';
11
12 use base 'Moose::Meta::Method',
13          'Class::MOP::Method::Accessor';
14
15 ## Inline method generators
16
17 sub generate_accessor_method_inline {
18     my $self        = $_[0];
19     my $attr        = $self->associated_attribute;
20     my $attr_name   = $attr->name;
21     my $inv         = '$_[0]';
22     my $slot_access = $self->_inline_access($inv, $attr_name);
23     my $value_name  = $attr->should_coerce ? '$val' : '$_[1]';
24
25     my $code = 'sub { ' . "\n"
26     . $self->_inline_pre_body(@_) . "\n"
27     . 'if (scalar(@_) == 2) {' . "\n"
28         . $self->_inline_check_required . "\n"
29         . $self->_inline_check_coercion . "\n"
30         . $self->_inline_check_constraint($value_name) . "\n"
31                 . $self->_inline_store($inv, $value_name) . "\n"
32                 . $self->_inline_trigger($inv, $value_name) . "\n"
33     . ' }' . "\n"
34     . $self->_inline_check_lazy . "\n"
35     . $self->_inline_post_body(@_) . "\n"
36     . 'return ' . $self->_inline_auto_deref($self->_inline_get($inv)) . "\n"
37     . ' }';
38
39     # NOTE:
40     # set up the environment
41     my $type_constraint = $attr->type_constraint
42                                 ? $attr->type_constraint->_compiled_type_constraint
43                                 : undef;
44
45     my $sub = eval $code;
46     confess "Could not create accessor for '$attr_name' because $@ \n code: $code" if $@;
47     return $sub;
48 }
49
50 sub generate_writer_method_inline {
51     my $self        = $_[0];
52     my $attr        = $self->associated_attribute;
53     my $attr_name   = $attr->name;
54     my $inv         = '$_[0]';
55     my $slot_access = $self->_inline_get($inv, $attr_name);
56     my $value_name  = $attr->should_coerce ? '$val' : '$_[1]';
57
58     my $code = 'sub { '
59     . $self->_inline_pre_body(@_)
60     . $self->_inline_check_required
61     . $self->_inline_check_coercion
62         . $self->_inline_check_constraint($value_name)
63         . $self->_inline_store($inv, $value_name)
64         . $self->_inline_post_body(@_)
65         . $self->_inline_trigger($inv, $value_name)
66     . ' }';
67
68     # NOTE:
69     # set up the environment
70     my $type_constraint = $attr->type_constraint
71                                 ? $attr->type_constraint->_compiled_type_constraint
72                                 : undef;
73
74     my $sub = eval $code;
75     confess "Could not create writer for '$attr_name' because $@ \n code: $code" if $@;
76     return $sub;
77 }
78
79 sub generate_reader_method_inline {
80     my $self        = $_[0];
81     my $attr        = $self->associated_attribute;
82     my $attr_name   = $attr->name;
83     my $inv         = '$_[0]';
84     my $slot_access = $self->_inline_get($inv, $attr_name);
85
86     my $code = 'sub {'
87     . $self->_inline_pre_body(@_)
88     . 'confess "Cannot assign a value to a read-only accessor" if @_ > 1;'
89     . $self->_inline_check_lazy
90     . $self->_inline_post_body(@_)
91     . 'return ' . $self->_inline_auto_deref( $slot_access ) . ';'
92     . '}';
93
94     # NOTE:
95     # set up the environment
96     my $type_constraint = $attr->type_constraint
97                                 ? $attr->type_constraint->_compiled_type_constraint
98                                 : undef;
99
100     my $sub = eval $code;
101     confess "Could not create reader for '$attr_name' because $@ \n code: $code" if $@;
102     return $sub;
103 }
104
105 sub generate_reader_method { shift->generate_reader_method_inline(@_) }
106 sub generate_writer_method { shift->generate_writer_method_inline(@_) }
107 sub generate_accessor_method { shift->generate_accessor_method_inline(@_) }
108
109 sub _inline_pre_body  { '' }
110 sub _inline_post_body { '' }
111
112 sub _inline_check_constraint {
113         my ($self, $value) = @_;
114
115         my $attr = $self->associated_attribute;
116
117         return '' unless $attr->has_type_constraint;
118
119         # FIXME
120         # This sprintf is insanely annoying, we should
121         # fix it someday - SL
122         return sprintf <<'EOF', $value, $value, $value, $value, $value, $value, $value
123 defined($type_constraint->(%s))
124         || confess "Attribute (" . $attr->name . ") does not pass the type constraint ("
125        . $attr->type_constraint->name . ") with "
126        . (defined(%s) ? (Scalar::Util::blessed(%s) && overload::Overloaded(%s) ? overload::StrVal(%s) : %s) : "undef")
127   if defined(%s);
128 EOF
129 }
130
131 sub _inline_check_coercion {
132         my $attr = (shift)->associated_attribute;
133
134         return '' unless $attr->should_coerce;
135     return 'my $val = $attr->type_constraint->coerce($_[1]);'
136 }
137
138 sub _inline_check_required {
139         my $attr = (shift)->associated_attribute;
140
141         return '' unless $attr->is_required;
142     return 'defined($_[1]) || confess "Attribute ($attr_name) is required, so cannot be set to undef";'
143 }
144
145 sub _inline_check_lazy {
146     my $self = $_[0];
147     my $attr = $self->associated_attribute;
148
149         return '' unless $attr->is_lazy;
150
151     my $inv         = '$_[0]';
152     my $slot_access = $self->_inline_access($inv, $attr->name);
153     my $slot_exists = $self->_inline_has($inv, $attr->name);
154         if ($attr->has_type_constraint) {
155             # NOTE:
156             # this could probably be cleaned
157             # up and streamlined a little more
158             return 'unless (' . $slot_exists . ') {' .
159                    '    if ($attr->has_default || $attr->has_builder ) {' .
160                    '        my $default; '.
161                    '        $default = $attr->default(' . $inv . ')  if $attr->has_default;' .
162                    '        if ( $attr->has_builder ) { '.
163                    '            my $builder = $attr->builder;'.
164                    '            if($builder = '.$inv.'->can($builder)){ '.
165                    '                $default = '.$inv.'->$builder; '.
166                    '            } else { '.
167                    '                confess(blessed('.$inv.')." does not support builder method \'$builder\' for attribute \'" . $attr->name . "\'");'.
168                    '            }'.
169                    '        }'.
170                    ($attr->should_coerce
171                        ? '$default = $attr->type_constraint->coerce($default);'
172                        : '') .
173                '        (defined($type_constraint->($default)))' .
174                '                || confess "Attribute (" . $attr->name . ") does not pass the type constraint ("' .
175                '               . $attr->type_constraint->name . ") with " . (defined($default) ? (Scalar::Util::blessed($default) && overload::Overloaded($default) ? overload::StrVal($default) : $default) : "undef")' .
176                '          if defined($default);' .
177                    '        ' . $slot_access . ' = $default; ' .
178                    '    }' .
179                    '    else {' .
180                '        ' . $slot_access . ' = undef;' .
181                    '    }' .
182                    '}';
183         }
184
185     return  'unless ( ' . $slot_exists . ') {' .
186             '    if ($attr->has_default) { ' . $slot_access . ' = $attr->default(' . $inv . '); }' .
187             '    elsif ($attr->has_builder) { '.
188             '        my $builder = $attr->builder; ' .
189             '        if($builder = '.$inv.'->can($builder)){ '.
190             '            ' . $slot_access . ' = ' . $inv . '->$builder; '.
191             '        } else { '.
192             '            confess(blessed('.$inv.')." does not support builder method \'$builder\' for attribute \'" . $attr->name . "\'");'.
193             '        }'.
194             '    } else { ' .$slot_access . ' = undef; } '.
195             '}';
196 }
197
198
199 sub _inline_store {
200         my ($self, $instance, $value) = @_;
201         my $attr = $self->associated_attribute;
202
203         my $mi = $attr->associated_class->get_meta_instance;
204         my $slot_name = sprintf "'%s'", $attr->slots;
205
206     my $code = $mi->inline_set_slot_value($instance, $slot_name, $value)    . ";";
207         $code   .= $mi->inline_weaken_slot_value($instance, $slot_name, $value) . ";"
208             if $attr->is_weak_ref;
209     return $code;
210 }
211
212 sub _inline_trigger {
213         my ($self, $instance, $value) = @_;
214         my $attr = $self->associated_attribute;
215         return '' unless $attr->has_trigger;
216         return sprintf('$attr->trigger->(%s, %s, $attr);', $instance, $value);
217 }
218
219 sub _inline_get {
220         my ($self, $instance) = @_;
221         my $attr = $self->associated_attribute;
222
223         my $mi = $attr->associated_class->get_meta_instance;
224         my $slot_name = sprintf "'%s'", $attr->slots;
225
226     return $mi->inline_get_slot_value($instance, $slot_name);
227 }
228
229 sub _inline_access {
230         my ($self, $instance) = @_;
231         my $attr = $self->associated_attribute;
232
233         my $mi = $attr->associated_class->get_meta_instance;
234         my $slot_name = sprintf "'%s'", $attr->slots;
235
236     return $mi->inline_slot_access($instance, $slot_name);
237 }
238
239 sub _inline_has {
240         my ($self, $instance) = @_;
241         my $attr = $self->associated_attribute;
242
243         my $mi = $attr->associated_class->get_meta_instance;
244         my $slot_name = sprintf "'%s'", $attr->slots;
245
246     return $mi->inline_is_slot_initialized($instance, $slot_name);
247 }
248
249 sub _inline_auto_deref {
250     my ( $self, $ref_value ) = @_;
251         my $attr = $self->associated_attribute;
252
253     return $ref_value unless $attr->should_auto_deref;
254
255     my $type_constraint = $attr->type_constraint;
256
257     my $sigil;
258     if ($type_constraint->is_a_type_of('ArrayRef')) {
259         $sigil = '@';
260     }
261     elsif ($type_constraint->is_a_type_of('HashRef')) {
262         $sigil = '%';
263     }
264     else {
265         confess "Can not auto de-reference the type constraint '" . $type_constraint->name . "'";
266     }
267
268     "(wantarray() ? $sigil\{ ( $ref_value ) || return } : ( $ref_value ) )";
269 }
270
271 1;
272
273 __END__
274
275 =pod
276
277 =head1 NAME
278
279 Moose::Meta::Method::Accessor - A Moose Method metaclass for accessors
280
281 =head1 DESCRIPTION
282
283 This is a subclass of L<Class::MOP::Method::Accessor> and it's primary
284 responsibility is to generate the accessor methods for attributes. It
285 can handle both closure based accessors, as well as inlined source based
286 accessors.
287
288 This is a fairly new addition to the MOP, but this will play an important
289 role in the optimization strategy we are currently following.
290
291 =head1 METHODS
292
293 =over 4
294
295 =item B<generate_accessor_method>
296
297 =item B<generate_reader_method>
298
299 =item B<generate_writer_method>
300
301 =item B<generate_accessor_method_inline>
302
303 =item B<generate_reader_method_inline>
304
305 =item B<generate_writer_method_inline>
306
307 =back
308
309 =head1 BUGS
310
311 All complex software has bugs lurking in it, and this module is no
312 exception. If you find a bug please either email me, or add the bug
313 to cpan-RT.
314
315 =head1 AUTHOR
316
317 Stevan Little E<lt>stevan@iinteractive.comE<gt>
318
319 Yuval Kogman E<lt>nothingmuch@woobling.comE<gt>
320
321 =head1 COPYRIGHT AND LICENSE
322
323 Copyright 2006, 2007 by Infinity Interactive, Inc.
324
325 L<http://www.iinteractive.com>
326
327 This library is free software; you can redistribute it and/or modify
328 it under the same terms as Perl itself.
329
330 =cut