Add various docs about checking types of attributes against constraints
[gitmo/Moose.git] / lib / Moose / Meta / Attribute.pm
1
2 package Moose::Meta::Attribute;
3
4 use strict;
5 use warnings;
6
7 use Scalar::Util 'blessed', 'weaken', 'reftype';
8 use Carp         'confess';
9 use overload     ();
10
11 our $VERSION   = '0.11';
12 our $AUTHORITY = 'cpan:STEVAN';
13
14 use Moose::Meta::Method::Accessor;
15 use Moose::Util::TypeConstraints ();
16
17 use base 'Class::MOP::Attribute';
18
19 # options which are not directly used
20 # but we store them for metadata purposes
21 __PACKAGE__->meta->add_attribute('isa'  => (reader    => '_isa_metadata'));
22 __PACKAGE__->meta->add_attribute('does' => (reader    => '_does_metadata'));
23 __PACKAGE__->meta->add_attribute('is'   => (reader    => '_is_metadata'));
24
25 # these are actual options for the attrs
26 __PACKAGE__->meta->add_attribute('required'   => (reader => 'is_required'      ));
27 __PACKAGE__->meta->add_attribute('lazy'       => (reader => 'is_lazy'          ));
28 __PACKAGE__->meta->add_attribute('coerce'     => (reader => 'should_coerce'    ));
29 __PACKAGE__->meta->add_attribute('weak_ref'   => (reader => 'is_weak_ref'      ));
30 __PACKAGE__->meta->add_attribute('auto_deref' => (reader => 'should_auto_deref'));
31 __PACKAGE__->meta->add_attribute('type_constraint' => (
32     reader    => 'type_constraint',
33     predicate => 'has_type_constraint',
34 ));
35 __PACKAGE__->meta->add_attribute('trigger' => (
36     reader    => 'trigger',
37     predicate => 'has_trigger',
38 ));
39 __PACKAGE__->meta->add_attribute('handles' => (
40     reader    => 'handles',
41     predicate => 'has_handles',
42 ));
43 __PACKAGE__->meta->add_attribute('documentation' => (
44     reader    => 'documentation',
45     predicate => 'has_documentation',
46 ));
47
48 sub new {
49         my ($class, $name, %options) = @_;
50         $class->_process_options($name, \%options);
51         return $class->SUPER::new($name, %options);    
52 }
53
54 sub clone_and_inherit_options {
55     my ($self, %options) = @_;
56     # you can change default, required, coerce, documentation and lazy
57     my %actual_options;
58     foreach my $legal_option (qw(default coerce required documentation lazy)) {
59         if (exists $options{$legal_option}) {
60             $actual_options{$legal_option} = $options{$legal_option};
61             delete $options{$legal_option};
62         }
63     }
64     
65     # handles can only be added, not changed
66     if ($options{handles}) {
67         confess "You can only add the 'handles' option, you cannot change it"
68             if $self->has_handles;
69         $actual_options{handles} = $options{handles};
70         delete $options{handles};
71     }
72     
73     # isa can be changed, but only if the 
74     # new type is a subtype    
75     if ($options{isa}) {
76         my $type_constraint;
77             if (blessed($options{isa}) && $options{isa}->isa('Moose::Meta::TypeConstraint')) {
78                         $type_constraint = $options{isa};
79                 }        
80                 else {
81                     $type_constraint = Moose::Util::TypeConstraints::find_type_constraint($options{isa});
82                     (defined $type_constraint)
83                         || confess "Could not find the type constraint '" . $options{isa} . "'";
84                 }
85                 # NOTE:
86                 # check here to see if the new type 
87                 # is a subtype of the old one
88                 ($type_constraint->is_subtype_of($self->type_constraint->name))
89                     || confess "New type constraint setting must be a subtype of inherited one"
90                         # iff we have a type constraint that is ...
91                         if $self->has_type_constraint;
92                 # then we use it :)
93                 $actual_options{type_constraint} = $type_constraint;
94         delete $options{isa};
95     }
96     (scalar keys %options == 0) 
97         || confess "Illegal inherited options => (" . (join ', ' => keys %options) . ")";
98     $self->clone(%actual_options);
99 }
100
101 sub _process_options {
102     my ($class, $name, $options) = @_;
103     
104         if (exists $options->{is}) {
105                 if ($options->{is} eq 'ro') {
106                         $options->{reader} ||= $name;
107                         (!exists $options->{trigger})
108                             || confess "Cannot have a trigger on a read-only attribute";
109                 }
110                 elsif ($options->{is} eq 'rw') {
111                         $options->{accessor} = $name;                                           
112             ((reftype($options->{trigger}) || '') eq 'CODE')
113                 || confess "Trigger must be a CODE ref"
114                     if exists $options->{trigger};                      
115                 }
116                 else {
117                     confess "I do not understand this option (is => " . $options->{is} . ")"
118                 }                       
119         }
120         
121         if (exists $options->{isa}) {
122             
123             if (exists $options->{does}) {
124                 if (eval { $options->{isa}->can('does') }) {
125                     ($options->{isa}->does($options->{does}))               
126                         || confess "Cannot have an isa option and a does option if the isa does not do the does";
127                 }
128                 else {
129                     confess "Cannot have an isa option which cannot ->does()";
130                 }
131             }       
132             
133             # allow for anon-subtypes here ...
134             if (blessed($options->{isa}) && $options->{isa}->isa('Moose::Meta::TypeConstraint')) {
135                         $options->{type_constraint} = $options->{isa};
136                 }
137                 else {
138                     
139                     if ($options->{isa} =~ /\|/) {
140                         my @type_constraints = split /\s*\|\s*/ => $options->{isa};
141                         $options->{type_constraint} = Moose::Util::TypeConstraints::create_type_constraint_union(
142                             @type_constraints
143                         );
144                     }
145                     else {
146                     # otherwise assume it is a constraint
147                     my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options->{isa});           
148                     # if the constraing it not found ....
149                     unless (defined $constraint) {
150                         # assume it is a foreign class, and make 
151                         # an anon constraint for it 
152                         $constraint = Moose::Util::TypeConstraints::subtype(
153                             'Object', 
154                             Moose::Util::TypeConstraints::where { $_->isa($options->{isa}) }
155                         );
156                     }                       
157                 $options->{type_constraint} = $constraint;
158             }
159                 }
160         }       
161         elsif (exists $options->{does}) {           
162             # allow for anon-subtypes here ...
163             if (blessed($options->{does}) && $options->{does}->isa('Moose::Meta::TypeConstraint')) {
164                         $options->{type_constraint} = $options->{isa};
165                 }
166                 else {
167                     # otherwise assume it is a constraint
168                     my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options->{does});            
169                     # if the constraing it not found ....
170                     unless (defined $constraint) {                              
171                         # assume it is a foreign class, and make 
172                         # an anon constraint for it 
173                         $constraint = Moose::Util::TypeConstraints::subtype(
174                             'Role', 
175                             Moose::Util::TypeConstraints::where { $_->does($options->{does}) }
176                         );
177                     }                       
178             $options->{type_constraint} = $constraint;
179                 }           
180         }
181         
182         if (exists $options->{coerce} && $options->{coerce}) {
183             (exists $options->{type_constraint})
184                 || confess "You cannot have coercion without specifying a type constraint";             
185         confess "You cannot have a weak reference to a coerced value"
186             if $options->{weak_ref};            
187         }       
188         
189         if (exists $options->{auto_deref} && $options->{auto_deref}) {
190             (exists $options->{type_constraint})
191                 || confess "You cannot auto-dereference without specifying a type constraint";      
192             ($options->{type_constraint}->is_a_type_of('ArrayRef') ||
193          $options->{type_constraint}->is_a_type_of('HashRef'))
194                 || confess "You cannot auto-dereference anything other than a ArrayRef or HashRef";             
195         }
196         
197         if (exists $options->{lazy} && $options->{lazy}) {
198             (exists $options->{default})
199                 || confess "You cannot have lazy attribute without specifying a default value for it";      
200         }    
201 }
202
203 sub initialize_instance_slot {
204     my ($self, $meta_instance, $instance, $params) = @_;
205     my $init_arg = $self->init_arg();
206     # try to fetch the init arg from the %params ...
207
208     my $val;        
209     if (exists $params->{$init_arg}) {
210         $val = $params->{$init_arg};
211         
212         if (!defined $val && $self->is_required) {
213             confess "Attribute (" . $self->name . ") is required and cannot be undef";             
214         }
215     }
216     else {
217         # skip it if it's lazy
218         return if $self->is_lazy;
219         # and die if it's required and doesn't have a default value
220         confess "Attribute (" . $self->name . ") is required" 
221             if $self->is_required && !$self->has_default;
222     }
223
224     # if nothing was in the %params, we can use the 
225     # attribute's default value (if it has one)
226     if (!defined $val && $self->has_default) {
227         $val = $self->default($instance); 
228     }   
229     
230         if (defined $val || $self->has_default) {
231             if ($self->has_type_constraint) {
232                 my $type_constraint = $self->type_constraint;
233                     if ($self->should_coerce && $type_constraint->has_coercion) {
234                         $val = $type_constraint->coerce($val);
235                     }   
236             (defined($type_constraint->check($val))) 
237                 || confess "Attribute (" . 
238                            $self->name . 
239                            ") does not pass the type constraint (" . 
240                            $type_constraint->name .
241                            ") with '" . 
242                            (defined $val 
243                                ? (overload::Overloaded($val) 
244                                     ? overload::StrVal($val) 
245                                     : $val) 
246                                : 'undef') . 
247                            "'";                 
248         }
249         }
250
251     $meta_instance->set_slot_value($instance, $self->name, $val);
252     $meta_instance->weaken_slot_value($instance, $self->name) 
253         if ref $val && $self->is_weak_ref;
254 }
255
256 ## Slot management
257
258 sub set_value {
259     my ($self, $instance, $value) = @_;
260     
261     my $attr_name = $self->name;
262     
263     if ($self->is_required) {
264         defined($value) 
265             || confess "Attribute ($attr_name) is required, so cannot be set to undef";
266     }
267     
268     if ($self->has_type_constraint) {
269         
270         my $type_constraint = $self->type_constraint;
271         
272         if ($self->should_coerce) {
273             $value = $type_constraint->coerce($value);           
274         }
275         defined($type_constraint->_compiled_type_constraint->($value))
276                 || confess "Attribute ($attr_name) does not pass the type constraint ("
277                . $type_constraint->name 
278                . ") with " 
279                . (defined($value) 
280                     ? ("'" . (overload::Overloaded($value) ? overload::StrVal($value) : $value) . "'") 
281                     : "undef")
282           if defined($value);
283     }
284     
285     my $meta_instance = Class::MOP::Class->initialize(blessed($instance))
286                                          ->get_meta_instance;
287                                          
288     $meta_instance->set_slot_value($instance, $attr_name, $value);  
289       
290     if (ref $value && $self->is_weak_ref) {
291         $meta_instance->weaken_slot_value($instance, $attr_name);            
292     }
293     
294     if ($self->has_trigger) {
295         $self->trigger->($instance, $value, $self);
296     }
297 }
298
299 sub get_value {
300     my ($self, $instance) = @_;
301     
302     if ($self->is_lazy) {
303             unless ($self->has_value($instance)) {
304                 if ($self->has_default) {
305                     my $default = $self->default($instance);
306                     $self->set_value($instance, $default);
307                 }
308                 else {
309                 $self->set_value($instance, undef);
310                 }
311             }   
312     }
313     
314     if ($self->should_auto_deref) {
315         
316         my $type_constraint = $self->type_constraint;
317
318         if ($type_constraint->is_a_type_of('ArrayRef')) {
319             my $rv = $self->SUPER::get_value($instance);
320             return unless defined $rv;
321             return wantarray ? @{ $rv } : $rv;
322         } 
323         elsif ($type_constraint->is_a_type_of('HashRef')) {
324             my $rv = $self->SUPER::get_value($instance);
325             return unless defined $rv;
326             return wantarray ? %{ $rv } : $rv;
327         } 
328         else {
329             confess "Can not auto de-reference the type constraint '" . $type_constraint->name . "'";
330         }
331                
332     }
333     else {
334         
335         return $self->SUPER::get_value($instance);
336     }    
337 }
338
339 ## installing accessors 
340
341 sub accessor_metaclass { 'Moose::Meta::Method::Accessor' }
342
343 sub install_accessors {
344     my $self = shift;
345     $self->SUPER::install_accessors(@_);   
346     
347     if ($self->has_handles) {
348         
349         # NOTE:
350         # Here we canonicalize the 'handles' option
351         # this will sort out any details and always 
352         # return an hash of methods which we want 
353         # to delagate to, see that method for details
354         my %handles = $self->_canonicalize_handles();
355         
356         # find the name of the accessor for this attribute
357         my $accessor_name = $self->reader || $self->accessor;
358         (defined $accessor_name)
359             || confess "You cannot install delegation without a reader or accessor for the attribute";
360         
361         # make sure we handle HASH accessors correctly
362         ($accessor_name) = keys %{$accessor_name}
363             if ref($accessor_name) eq 'HASH';
364         
365         # install the delegation ...
366         my $associated_class = $self->associated_class;
367         foreach my $handle (keys %handles) {
368             my $method_to_call = $handles{$handle};
369             
370             (!$associated_class->has_method($handle))
371                 || confess "You cannot overwrite a locally defined method ($handle) with a delegation";
372             
373             # NOTE:
374             # handles is not allowed to delegate
375             # any of these methods, as they will 
376             # override the ones in your class, which 
377             # is almost certainly not what you want.
378             next if $handle =~ /^BUILD|DEMOLISH$/ || Moose::Object->can($handle);
379             
380             if ((reftype($method_to_call) || '') eq 'CODE') {
381                 $associated_class->add_method($handle => $method_to_call);                
382             }
383             else {
384                 $associated_class->add_method($handle => sub {
385                     # FIXME
386                     # we should check for lack of 
387                     # a callable return value from 
388                     # the accessor here 
389                     my $proxy = (shift)->$accessor_name();
390                     @_ = ($proxy, @_);
391                     goto &{ $proxy->can($method_to_call)};
392                 });
393             }
394         }
395     }
396     
397     return;
398 }
399
400 # private methods to help delegation ...
401
402 sub _canonicalize_handles {
403     my $self    = shift;
404     my $handles = $self->handles;
405     if (my $handle_type = ref($handles)) {
406         if ($handle_type eq 'HASH') {
407             return %{$handles};
408         }
409         elsif ($handle_type eq 'ARRAY') {
410             return map { $_ => $_ } @{$handles};
411         }
412         elsif ($handle_type eq 'Regexp') {
413             ($self->has_type_constraint)
414                 || confess "Cannot delegate methods based on a RegExpr without a type constraint (isa)";
415             return map  { ($_ => $_) } 
416                    grep { /$handles/ } $self->_get_delegate_method_list;
417         }
418         elsif ($handle_type eq 'CODE') {
419             return $handles->($self, $self->_find_delegate_metaclass);
420         }
421         else {
422             confess "Unable to canonicalize the 'handles' option with $handles";
423         }
424     }
425     else {
426         my $role_meta = eval { $handles->meta };
427         if ($@) {
428             confess "Unable to canonicalize the 'handles' option with $handles because : $@";            
429         }
430
431         (blessed $role_meta && $role_meta->isa('Moose::Meta::Role'))
432             || confess "Unable to canonicalize the 'handles' option with $handles because ->meta is not a Moose::Meta::Role";
433         
434         return map { $_ => $_ } (
435             $role_meta->get_method_list, 
436             $role_meta->get_required_method_list
437         );
438     }
439 }
440
441 sub _find_delegate_metaclass {
442     my $self = shift;
443     if (my $class = $self->_isa_metadata) {
444         # if the class does have 
445         # a meta method, use it
446         return $class->meta if $class->can('meta');
447         # otherwise we might be 
448         # dealing with a non-Moose
449         # class, and need to make 
450         # our own metaclass
451         return Moose::Meta::Class->initialize($class);
452     }
453     elsif (my $role = $self->_does_metadata) {
454         # our role will always have 
455         # a meta method
456         return $role->meta;
457     }
458     else {
459         confess "Cannot find delegate metaclass for attribute " . $self->name;
460     }
461 }
462
463 sub _get_delegate_method_list {
464     my $self = shift;
465     my $meta = $self->_find_delegate_metaclass;
466     if ($meta->isa('Class::MOP::Class')) {
467         return map  { $_->{name}                     }  # NOTE: !never! delegate &meta
468                grep { $_->{class} ne 'Moose::Object' && $_->{name} ne 'meta' } 
469                     $meta->compute_all_applicable_methods;
470     }
471     elsif ($meta->isa('Moose::Meta::Role')) {
472         return $meta->get_method_list;        
473     }
474     else {
475         confess "Unable to recognize the delegate metaclass '$meta'";
476     }
477 }
478
479 1;
480
481 __END__
482
483 =pod
484
485 =head1 NAME
486
487 Moose::Meta::Attribute - The Moose attribute metaclass
488
489 =head1 DESCRIPTION
490
491 This is a subclass of L<Class::MOP::Attribute> with Moose specific 
492 extensions. 
493
494 For the most part, the only time you will ever encounter an 
495 instance of this class is if you are doing some serious deep 
496 introspection. To really understand this class, you need to refer 
497 to the L<Class::MOP::Attribute> documentation.
498
499 =head1 METHODS
500
501 =head2 Overridden methods
502
503 These methods override methods in L<Class::MOP::Attribute> and add 
504 Moose specific features. You can safely assume though that they 
505 will behave just as L<Class::MOP::Attribute> does.
506
507 =over 4
508
509 =item B<new>
510
511 =item B<initialize_instance_slot>
512
513 =item B<install_accessors>
514
515 =item B<accessor_metaclass>
516
517 =item B<get_value>
518
519 =item B<set_value>
520
521   eval { $point->meta->get_attribute('x')->set_value($point, 'fourty-two') };
522   if($@) {
523     print "Oops: $@\n";
524   }
525
526 I<Attribute (x) does not pass the type constraint (Int) with 'fourty-two'>
527
528 Before setting the value, a check is made on the type constraint of
529 the attribute, if it has one, to see if the value passes it. If the
530 value fails to pass, the set operation dies with a L<Carp/confess>.
531
532 Any coercion to convert values is done before checking the type constraint.
533
534 To check a value against a type constraint before setting it, fetch the
535 attribute instance using L<Moose::Meta::Attribute/find_attribute_by_name>,
536 fetch the type_constraint from the attribute using L<Moose::Meta::Attribute/type_constraint>
537 and call L<Moose::Meta::TypeConstraint/check>. See L<Moose::Cookbook::RecipeX>
538 for an example.
539
540 =back
541
542 =head2 Additional Moose features
543
544 Moose attributes support type-constraint checking, weak reference 
545 creation and type coercion.  
546
547 =over 4
548
549 =item B<clone_and_inherit_options>
550
551 This is to support the C<has '+foo'> feature, it clones an attribute 
552 from a superclass and allows a very specific set of changes to be made 
553 to the attribute.
554
555 =item B<has_type_constraint>
556
557 Returns true if this meta-attribute has a type constraint.
558
559 =item B<type_constraint>
560
561 A read-only accessor for this meta-attribute's type constraint. For 
562 more information on what you can do with this, see the documentation 
563 for L<Moose::Meta::TypeConstraint>.
564
565 =item B<has_handles>
566
567 Returns true if this meta-attribute performs delegation.
568
569 =item B<handles>
570
571 This returns the value which was passed into the handles option.
572
573 =item B<is_weak_ref>
574
575 Returns true if this meta-attribute produces a weak reference.
576
577 =item B<is_required>
578
579 Returns true if this meta-attribute is required to have a value.
580
581 =item B<is_lazy>
582
583 Returns true if this meta-attribute should be initialized lazily.
584
585 NOTE: lazy attributes, B<must> have a C<default> field set.
586
587 =item B<should_coerce>
588
589 Returns true if this meta-attribute should perform type coercion.
590
591 =item B<should_auto_deref>
592
593 Returns true if this meta-attribute should perform automatic 
594 auto-dereferencing. 
595
596 NOTE: This can only be done for attributes whose type constraint is 
597 either I<ArrayRef> or I<HashRef>.
598
599 =item B<has_trigger>
600
601 Returns true if this meta-attribute has a trigger set.
602
603 =item B<trigger>
604
605 This is a CODE reference which will be executed every time the 
606 value of an attribute is assigned. The CODE ref will get two values, 
607 the invocant and the new value. This can be used to handle I<basic> 
608 bi-directional relations.
609
610 =item B<documentation>
611
612 This is a string which contains the documentation for this attribute. 
613 It serves no direct purpose right now, but it might in the future
614 in some kind of automated documentation system perhaps.
615
616 =item B<has_documentation>
617
618 Returns true if this meta-attribute has any documentation.
619
620 =back
621
622 =head1 BUGS
623
624 All complex software has bugs lurking in it, and this module is no 
625 exception. If you find a bug please either email me, or add the bug
626 to cpan-RT.
627
628 =head1 AUTHOR
629
630 Stevan Little E<lt>stevan@iinteractive.comE<gt>
631
632 Yuval Kogman E<lt>nothingmuch@woobling.comE<gt>
633
634 =head1 COPYRIGHT AND LICENSE
635
636 Copyright 2006, 2007 by Infinity Interactive, Inc.
637
638 L<http://www.iinteractive.com>
639
640 This library is free software; you can redistribute it and/or modify
641 it under the same terms as Perl itself. 
642
643 =cut