failing test for inheriting from non Mouse class
[gitmo/Mouse.git] / lib / Mouse / Meta / Attribute.pm
CommitLineData
c3398f5b 1#!/usr/bin/env perl
306290e8 2package Mouse::Meta::Attribute;
c3398f5b 3use strict;
4use warnings;
5
6use Carp 'confess';
3301fa54 7use Scalar::Util 'blessed';
c3398f5b 8
9sub new {
10 my $class = shift;
11 my %args = @_;
12
7ee01d77 13 $args{init_arg} = $args{name}
14 unless exists $args{init_arg};
45959ffa 15
c3398f5b 16 $args{is} ||= '';
17
45959ffa 18 if ( $args{lazy_build} ) {
19 $args{lazy} = 1;
20 $args{builder} ||= "_build_$args{name}";
21 }
22
c3398f5b 23 bless \%args, $class;
24}
25
3cf68001 26sub name { $_[0]->{name} }
27sub class { $_[0]->{class} }
28sub _is_metadata { $_[0]->{is} }
29sub is_required { $_[0]->{required} }
30sub default { $_[0]->{default} }
31sub is_lazy { $_[0]->{lazy} }
32sub predicate { $_[0]->{predicate} }
33sub clearer { $_[0]->{clearer} }
34sub handles { $_[0]->{handles} }
35sub is_weak_ref { $_[0]->{weak_ref} }
36sub init_arg { $_[0]->{init_arg} }
37sub type_constraint { $_[0]->{type_constraint} }
38sub trigger { $_[0]->{trigger} }
39sub builder { $_[0]->{builder} }
40sub should_auto_deref { $_[0]->{auto_deref} }
c3398f5b 41
ccea8101 42sub has_default { exists $_[0]->{default} }
43sub has_predicate { exists $_[0]->{predicate} }
44sub has_clearer { exists $_[0]->{clearer} }
45sub has_handles { exists $_[0]->{handles} }
ccea8101 46sub has_type_constraint { exists $_[0]->{type_constraint} }
de9a434a 47sub has_trigger { exists $_[0]->{trigger} }
48sub has_builder { exists $_[0]->{builder} }
ccea8101 49
1bfebf5f 50sub _create_args {
51 $_[0]->{_create_args} = $_[1] if @_ > 1;
52 $_[0]->{_create_args}
53}
54
c3398f5b 55sub generate_accessor {
56 my $attribute = shift;
57
2434d21b 58 my $name = $attribute->name;
f3c1ccc8 59 my $key = $name;
2434d21b 60 my $default = $attribute->default;
61 my $trigger = $attribute->trigger;
62 my $type = $attribute->type_constraint;
5aa30ced 63 my $constraint = $attribute->find_type_constraint;
9367e029 64 my $builder = $attribute->builder;
c3398f5b 65
66 my $accessor = 'sub {
67 my $self = shift;';
68
2434d21b 69 if ($attribute->_is_metadata eq 'rw') {
c3398f5b 70 $accessor .= 'if (@_) {
a707f587 71 local $_ = $_[0];';
72
73 if ($constraint) {
1f2b4780 74 $accessor .= 'do {
e8b3db47 75 my $display = defined($_) ? overload::StrVal($_) : "undef";
1f2b4780 76 Carp::confess("Attribute ($name) does not pass the type constraint because: Validation failed for \'$type\' failed with value $display") unless $constraint->();
77 };'
a707f587 78 }
79
80 $accessor .= '$self->{$key} = $_;';
c3398f5b 81
3645b316 82 if ($attribute->is_weak_ref) {
83 $accessor .= 'Scalar::Util::weaken($self->{$key}) if ref($self->{$key});';
c3398f5b 84 }
85
86 if ($trigger) {
a707f587 87 $accessor .= '$trigger->($self, $_, $attribute);';
c3398f5b 88 }
89
90 $accessor .= '}';
91 }
92 else {
636c002e 93 $accessor .= 'confess "Cannot assign a value to a read-only accessor" if @_;';
c3398f5b 94 }
95
2434d21b 96 if ($attribute->is_lazy) {
c3398f5b 97 $accessor .= '$self->{$key} = ';
9367e029 98
99 $accessor .= $attribute->has_builder
100 ? '$self->$builder'
101 : ref($default) eq 'CODE'
102 ? '$default->($self)'
103 : '$default';
104
c3398f5b 105 $accessor .= ' if !exists($self->{$key});';
106 }
107
3cf68001 108 if ($attribute->should_auto_deref) {
109 if ($attribute->type_constraint eq 'ArrayRef') {
110 $accessor .= 'if (wantarray) {
111 return @{ $self->{$key} || [] };
112 }';
113 }
114 else {
115 $accessor .= 'if (wantarray) {
116 return %{ $self->{$key} || {} };
117 }';
118 }
119 }
120
121 $accessor .= 'return $self->{$key};
c3398f5b 122 }';
123
124 return eval $accessor;
125}
126
127sub generate_predicate {
128 my $attribute = shift;
f3c1ccc8 129 my $key = $attribute->name;
c3398f5b 130
131 my $predicate = 'sub { exists($_[0]->{$key}) }';
132
133 return eval $predicate;
134}
135
136sub generate_clearer {
137 my $attribute = shift;
f3c1ccc8 138 my $key = $attribute->name;
c3398f5b 139
140 my $predicate = 'sub { delete($_[0]->{$key}) }';
141
142 return eval $predicate;
143}
144
145sub generate_handles {
146 my $attribute = shift;
2434d21b 147 my $reader = $attribute->name;
c3cc3642 148 my %handles = $attribute->_canonicalize_handles($attribute->handles);
c3398f5b 149
150 my %method_map;
151
c3cc3642 152 for my $local_method (keys %handles) {
153 my $remote_method = $handles{$local_method};
c3398f5b 154
155 my $method = 'sub {
156 my $self = shift;
157 $self->$reader->$remote_method(@_)
158 }';
159
160 $method_map{$local_method} = eval $method;
161 }
162
163 return \%method_map;
164}
165
166sub create {
167 my ($self, $class, $name, %args) = @_;
168
1bfebf5f 169 $args{name} = $name;
170 $args{class} = $class;
171
8fd9e611 172 $self->validate_args($name, %args);
45ea8620 173
c021207d 174 $args{type_constraint} = delete $args{isa}
175 if exists $args{isa};
186657a9 176
1bfebf5f 177 my $attribute = $self->new(%args);
178 $attribute->_create_args(\%args);
179
c3398f5b 180 my $meta = $class->meta;
181
b2500191 182 $meta->add_attribute($attribute);
183
c3398f5b 184 # install an accessor
2434d21b 185 if ($attribute->_is_metadata eq 'rw' || $attribute->_is_metadata eq 'ro') {
c3398f5b 186 my $accessor = $attribute->generate_accessor;
187 no strict 'refs';
188 *{ $class . '::' . $name } = $accessor;
189 }
190
c3398f5b 191 for my $method (qw/predicate clearer/) {
2434d21b 192 my $predicate = "has_$method";
193 if ($attribute->$predicate) {
c3398f5b 194 my $generator = "generate_$method";
195 my $coderef = $attribute->$generator;
196 no strict 'refs';
2434d21b 197 *{ $class . '::' . $attribute->$method } = $coderef;
c3398f5b 198 }
199 }
200
2434d21b 201 if ($attribute->has_handles) {
c3398f5b 202 my $method_map = $attribute->generate_handles;
203 for my $method_name (keys %$method_map) {
204 no strict 'refs';
205 *{ $class . '::' . $method_name } = $method_map->{$method_name};
206 }
207 }
208
209 return $attribute;
210}
211
8fd9e611 212sub validate_args {
213 my $self = shift;
214 my $name = shift;
215 my %args = @_;
216
217 confess "You cannot have lazy attribute ($name) without specifying a default value for it"
218 if $args{lazy} && !exists($args{default}) && !exists($args{builder});
219
220 confess "References are not allowed as default values, you must wrap the default of '$name' in a CODE reference (ex: sub { [] } and not [])"
221 if ref($args{default})
222 && ref($args{default}) ne 'CODE';
223
224 confess "You cannot auto-dereference without specifying a type constraint on attribute $name"
225 if $args{auto_deref} && !exists($args{isa});
226
227 confess "You cannot auto-dereference anything other than a ArrayRef or HashRef on attribute $name"
228 if $args{auto_deref}
229 && $args{isa} ne 'ArrayRef'
230 && $args{isa} ne 'HashRef';
231
232 return 1;
233}
234
5aa30ced 235sub find_type_constraint {
236 my $self = shift;
237 my $type = $self->type_constraint;
238
239 return unless $type;
240
241 my $checker = Mouse::TypeRegistry->optimized_constraints->{$type};
242 return $checker if $checker;
243
3301fa54 244 return sub { blessed($_) && blessed($_) eq $type };
5aa30ced 245}
246
247sub verify_type_constraint {
248 my $self = shift;
249 local $_ = shift;
250
251 my $type = $self->type_constraint
252 or return 1;
af745d5a 253 my $constraint = $self->find_type_constraint;
5aa30ced 254
255 return 1 if $constraint->($_);
256
257 my $name = $self->name;
f3e05dfd 258 my $display = defined($_) ? overload::StrVal($_) : 'undef';
259 Carp::confess("Attribute ($name) does not pass the type constraint because: Validation failed for \'$type\' failed with value $display");
5aa30ced 260}
261
af745d5a 262sub _canonicalize_handles {
263 my $self = shift;
264 my $handles = shift;
265
266 if (ref($handles) eq 'HASH') {
267 return %$handles;
268 }
269 elsif (ref($handles) eq 'ARRAY') {
270 return map { $_ => $_ } @$handles;
271 }
272 else {
273 confess "Unable to canonicalize the 'handles' option with $handles";
274 }
275}
276
1bfebf5f 277sub clone_parent {
278 my $self = shift;
279 my $class = shift;
280 my $name = shift;
281 my %args = ($self->get_parent_args($class, $name), @_);
282
283 $self->create($class, $name, %args);
284}
285
286sub get_parent_args {
287 my $self = shift;
288 my $class = shift;
289 my $name = shift;
290
291 for my $super ($class->meta->linearized_isa) {
292 my $super_attr = $super->meta->get_attribute($name)
293 or next;
294 return %{ $super_attr->_create_args };
295 }
296
297 confess "Could not find an attribute by the name of '$name' to inherit from";
298}
299
c3398f5b 3001;
301
302__END__
303
304=head1 NAME
305
306290e8 306Mouse::Meta::Attribute - attribute metaclass
c3398f5b 307
308=head1 METHODS
309
306290e8 310=head2 new %args -> Mouse::Meta::Attribute
c3398f5b 311
306290e8 312Instantiates a new Mouse::Meta::Attribute. Does nothing else.
c3398f5b 313
306290e8 314=head2 create OwnerClass, AttributeName, %args -> Mouse::Meta::Attribute
c3398f5b 315
316Creates a new attribute in OwnerClass. Accessors and helper methods are
317installed. Some error checking is done.
318
319=head2 name -> AttributeName
320
321=head2 class -> OwnerClass
322
ab27a55e 323=head2 is_required -> Bool
c3398f5b 324
ab27a55e 325=head2 default -> Item
c3398f5b 326
ab27a55e 327=head2 has_default -> Bool
328
329=head2 is_lazy -> Bool
330
331=head2 predicate -> MethodName | Undef
332
333=head2 has_predicate -> Bool
334
335=head2 clearer -> MethodName | Undef
336
337=head2 has_clearer -> Bool
c3398f5b 338
339=head2 handles -> { LocalName => RemoteName }
340
ab27a55e 341=head2 has_handles -> Bool
342
3645b316 343=head2 is_weak_ref -> Bool
c3398f5b 344
345=head2 init_arg -> Str
346
ab27a55e 347=head2 type_constraint -> Str
348
349=head2 has_type_constraint -> Bool
350
351=head2 trigger => CODE | Undef
352
353=head2 has_trigger -> Bool
354
355=head2 builder => MethodName | Undef
356
357=head2 has_builder -> Bool
358
0fff36e6 359=head2 should_auto_deref -> Bool
360
c3398f5b 361Informational methods.
362
363=head2 generate_accessor -> CODE
364
365Creates a new code reference for the attribute's accessor.
366
367=head2 generate_predicate -> CODE
368
369Creates a new code reference for the attribute's predicate.
370
371=head2 generate_clearer -> CODE
372
373Creates a new code reference for the attribute's clearer.
374
375=head2 generate_handles -> { MethodName => CODE }
376
377Creates a new code reference for each of the attribute's handles methods.
378
fb706f5c 379=head2 find_type_constraint -> CODE
380
381Returns a code reference which can be used to check that a given value passes
382this attribute's type constraint;
383
384=head2 verify_type_constraint Item -> 1 | ERROR
385
386Checks that the given value passes this attribute's type constraint. Returns 1
387on success, otherwise C<confess>es.
388
c3398f5b 389=cut
390