504ae3684248e12c1b53f97e2a9922003bf475dd
[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_get($inv, $attr_name);
23     my $value_name  = $attr->should_coerce ? '$val' : '$_[1]';
24
25     my $code = 'sub { '
26     . $self->_inline_pre_body(@_)
27     . 'if (scalar(@_) == 2) {'
28         . $self->_inline_check_required
29         . $self->_inline_check_coercion
30         . $self->_inline_check_constraint($value_name)
31                 . $self->_inline_store($inv, $value_name)
32                 . $self->_inline_trigger($inv, $value_name)
33     . ' }'
34     . $self->_inline_check_lazy
35     . $self->_inline_post_body(@_)
36     . 'return ' . $self->_inline_auto_deref($self->_inline_get($inv))
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_get($inv, $attr->name);
153
154         if ($attr->has_type_constraint) {
155             # NOTE:
156             # this could probably be cleaned
157             # up and streamlined a little more
158             return 'unless (exists ' . $slot_access . ') {' .
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 = $self->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 (exists ' . $slot_access . ') {' .
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_auto_deref {
230     my ( $self, $ref_value ) = @_;
231         my $attr = $self->associated_attribute;
232
233     return $ref_value unless $attr->should_auto_deref;
234
235     my $type_constraint = $attr->type_constraint;
236
237     my $sigil;
238     if ($type_constraint->is_a_type_of('ArrayRef')) {
239         $sigil = '@';
240     }
241     elsif ($type_constraint->is_a_type_of('HashRef')) {
242         $sigil = '%';
243     }
244     else {
245         confess "Can not auto de-reference the type constraint '" . $type_constraint->name . "'";
246     }
247
248     "(wantarray() ? $sigil\{ ( $ref_value ) || return } : ( $ref_value ) )";
249 }
250
251 1;
252
253 __END__
254
255 =pod
256
257 =head1 NAME
258
259 Moose::Meta::Method::Accessor - A Moose Method metaclass for accessors
260
261 =head1 DESCRIPTION
262
263 This is a subclass of L<Class::MOP::Method::Accessor> and it's primary
264 responsibility is to generate the accessor methods for attributes. It
265 can handle both closure based accessors, as well as inlined source based
266 accessors.
267
268 This is a fairly new addition to the MOP, but this will play an important
269 role in the optimization strategy we are currently following.
270
271 =head1 METHODS
272
273 =over 4
274
275 =item B<generate_accessor_method>
276
277 =item B<generate_reader_method>
278
279 =item B<generate_writer_method>
280
281 =item B<generate_accessor_method_inline>
282
283 =item B<generate_reader_method_inline>
284
285 =item B<generate_writer_method_inline>
286
287 =back
288
289 =head1 BUGS
290
291 All complex software has bugs lurking in it, and this module is no
292 exception. If you find a bug please either email me, or add the bug
293 to cpan-RT.
294
295 =head1 AUTHOR
296
297 Stevan Little E<lt>stevan@iinteractive.comE<gt>
298
299 Yuval Kogman E<lt>nothingmuch@woobling.comE<gt>
300
301 =head1 COPYRIGHT AND LICENSE
302
303 Copyright 2006, 2007 by Infinity Interactive, Inc.
304
305 L<http://www.iinteractive.com>
306
307 This library is free software; you can redistribute it and/or modify
308 it under the same terms as Perl itself.
309
310 =cut