Add various docs about checking types of attributes against constraints
[gitmo/Moose.git] / lib / Moose / Meta / Attribute.pm
CommitLineData
c0e30cf5 1
2package Moose::Meta::Attribute;
3
4use strict;
5use warnings;
6
78cd1d3b 7use Scalar::Util 'blessed', 'weaken', 'reftype';
a15dff8d 8use Carp 'confess';
a909a4df 9use overload ();
a15dff8d 10
c84f324f 11our $VERSION = '0.11';
d44714be 12our $AUTHORITY = 'cpan:STEVAN';
78cd1d3b 13
8ee73eeb 14use Moose::Meta::Method::Accessor;
a3c7e2fe 15use Moose::Util::TypeConstraints ();
bc1e29b5 16
c0e30cf5 17use base 'Class::MOP::Attribute';
18
452bac1b 19# options which are not directly used
20# but we store them for metadata purposes
98aae381 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'));
452bac1b 24
25# these are actual options for the attrs
1a563243 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'));
82168dbb 31__PACKAGE__->meta->add_attribute('type_constraint' => (
32 reader => 'type_constraint',
33 predicate => 'has_type_constraint',
34));
8c9d74e7 35__PACKAGE__->meta->add_attribute('trigger' => (
36 reader => 'trigger',
37 predicate => 'has_trigger',
38));
452bac1b 39__PACKAGE__->meta->add_attribute('handles' => (
40 reader => 'handles',
41 predicate => 'has_handles',
42));
ddbdc0cb 43__PACKAGE__->meta->add_attribute('documentation' => (
44 reader => 'documentation',
45 predicate => 'has_documentation',
46));
82168dbb 47
78cd1d3b 48sub new {
49 my ($class, $name, %options) = @_;
1d768fb1 50 $class->_process_options($name, \%options);
98aae381 51 return $class->SUPER::new($name, %options);
1d768fb1 52}
53
ce0e8d63 54sub clone_and_inherit_options {
55 my ($self, %options) = @_;
83cc9094 56 # you can change default, required, coerce, documentation and lazy
ce0e8d63 57 my %actual_options;
83cc9094 58 foreach my $legal_option (qw(default coerce required documentation lazy)) {
ce0e8d63 59 if (exists $options{$legal_option}) {
60 $actual_options{$legal_option} = $options{$legal_option};
61 delete $options{$legal_option};
62 }
63 }
83cc9094 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
fcb7afc2 73 # isa can be changed, but only if the
74 # new type is a subtype
ce0e8d63 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 }
2a0f3bd3 85 # NOTE:
86 # check here to see if the new type
87 # is a subtype of the old one
ce0e8d63 88 ($type_constraint->is_subtype_of($self->type_constraint->name))
89 || confess "New type constraint setting must be a subtype of inherited one"
2a0f3bd3 90 # iff we have a type constraint that is ...
ce0e8d63 91 if $self->has_type_constraint;
2a0f3bd3 92 # then we use it :)
ce0e8d63 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);
1d768fb1 99}
100
101sub _process_options {
102 my ($class, $name, $options) = @_;
452bac1b 103
1d768fb1 104 if (exists $options->{is}) {
105 if ($options->{is} eq 'ro') {
df492bba 106 $options->{reader} ||= $name;
1d768fb1 107 (!exists $options->{trigger})
8c9d74e7 108 || confess "Cannot have a trigger on a read-only attribute";
78cd1d3b 109 }
1d768fb1 110 elsif ($options->{is} eq 'rw') {
452bac1b 111 $options->{accessor} = $name;
98aae381 112 ((reftype($options->{trigger}) || '') eq 'CODE')
113 || confess "Trigger must be a CODE ref"
114 if exists $options->{trigger};
452bac1b 115 }
116 else {
117 confess "I do not understand this option (is => " . $options->{is} . ")"
78cd1d3b 118 }
119 }
120
1d768fb1 121 if (exists $options->{isa}) {
02a0fb52 122
1d768fb1 123 if (exists $options->{does}) {
124 if (eval { $options->{isa}->can('does') }) {
125 ($options->{isa}->does($options->{does}))
02a0fb52 126 || confess "Cannot have an isa option and a does option if the isa does not do the does";
127 }
7eaef7ad 128 else {
129 confess "Cannot have an isa option which cannot ->does()";
130 }
02a0fb52 131 }
132
78cd1d3b 133 # allow for anon-subtypes here ...
1d768fb1 134 if (blessed($options->{isa}) && $options->{isa}->isa('Moose::Meta::TypeConstraint')) {
135 $options->{type_constraint} = $options->{isa};
78cd1d3b 136 }
137 else {
c07af9d2 138
1d768fb1 139 if ($options->{isa} =~ /\|/) {
140 my @type_constraints = split /\s*\|\s*/ => $options->{isa};
141 $options->{type_constraint} = Moose::Util::TypeConstraints::create_type_constraint_union(
c07af9d2 142 @type_constraints
78cd1d3b 143 );
c07af9d2 144 }
145 else {
146 # otherwise assume it is a constraint
1d768fb1 147 my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options->{isa});
c07af9d2 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',
1d768fb1 154 Moose::Util::TypeConstraints::where { $_->isa($options->{isa}) }
c07af9d2 155 );
156 }
1d768fb1 157 $options->{type_constraint} = $constraint;
c07af9d2 158 }
78cd1d3b 159 }
160 }
1d768fb1 161 elsif (exists $options->{does}) {
02a0fb52 162 # allow for anon-subtypes here ...
1d768fb1 163 if (blessed($options->{does}) && $options->{does}->isa('Moose::Meta::TypeConstraint')) {
164 $options->{type_constraint} = $options->{isa};
02a0fb52 165 }
166 else {
167 # otherwise assume it is a constraint
1d768fb1 168 my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options->{does});
02a0fb52 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',
1d768fb1 175 Moose::Util::TypeConstraints::where { $_->does($options->{does}) }
02a0fb52 176 );
177 }
1d768fb1 178 $options->{type_constraint} = $constraint;
02a0fb52 179 }
180 }
78cd1d3b 181
1d768fb1 182 if (exists $options->{coerce} && $options->{coerce}) {
183 (exists $options->{type_constraint})
3ec7b7a3 184 || confess "You cannot have coercion without specifying a type constraint";
4b598ea3 185 confess "You cannot have a weak reference to a coerced value"
1d768fb1 186 if $options->{weak_ref};
ca01a97b 187 }
78cd1d3b 188
536f0b17 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";
94b8bbb8 192 ($options->{type_constraint}->is_a_type_of('ArrayRef') ||
193 $options->{type_constraint}->is_a_type_of('HashRef'))
536f0b17 194 || confess "You cannot auto-dereference anything other than a ArrayRef or HashRef";
195 }
196
1d768fb1 197 if (exists $options->{lazy} && $options->{lazy}) {
198 (exists $options->{default})
ca01a97b 199 || confess "You cannot have lazy attribute without specifying a default value for it";
1d768fb1 200 }
78cd1d3b 201}
c0e30cf5 202
d500266f 203sub initialize_instance_slot {
ddd0ec20 204 my ($self, $meta_instance, $instance, $params) = @_;
d500266f 205 my $init_arg = $self->init_arg();
206 # try to fetch the init arg from the %params ...
ddd0ec20 207
d500266f 208 my $val;
209 if (exists $params->{$init_arg}) {
210 $val = $params->{$init_arg};
ab859145 211
212 if (!defined $val && $self->is_required) {
213 confess "Attribute (" . $self->name . ") is required and cannot be undef";
214 }
d500266f 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 }
ddd0ec20 223
d500266f 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);
ab859145 228 }
d7611a4a 229
7a5ebc40 230 if (defined $val || $self->has_default) {
d500266f 231 if ($self->has_type_constraint) {
c07af9d2 232 my $type_constraint = $self->type_constraint;
233 if ($self->should_coerce && $type_constraint->has_coercion) {
0a5bd159 234 $val = $type_constraint->coerce($val);
d500266f 235 }
c07af9d2 236 (defined($type_constraint->check($val)))
237 || confess "Attribute (" .
238 $self->name .
8449e6e7 239 ") does not pass the type constraint (" .
c07af9d2 240 $type_constraint->name .
a909a4df 241 ") with '" .
242 (defined $val
243 ? (overload::Overloaded($val)
244 ? overload::StrVal($val)
245 : $val)
246 : 'undef') .
247 "'";
d500266f 248 }
249 }
ddd0ec20 250
ac1ef2f9 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;
d500266f 254}
255
d617b644 256## Slot management
9e93dd19 257
946289d1 258sub 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 ("
a909a4df 277 . $type_constraint->name
278 . ") with "
279 . (defined($value)
280 ? ("'" . (overload::Overloaded($value) ? overload::StrVal($value) : $value) . "'")
281 : "undef")
946289d1 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
299sub 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}
a15dff8d 338
d617b644 339## installing accessors
c0e30cf5 340
d617b644 341sub accessor_metaclass { 'Moose::Meta::Method::Accessor' }
d7f17ebb 342
452bac1b 343sub 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
d022f632 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
452bac1b 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 {
b805c70c 385 # FIXME
386 # we should check for lack of
387 # a callable return value from
388 # the accessor here
cbe25729 389 my $proxy = (shift)->$accessor_name();
390 @_ = ($proxy, @_);
391 goto &{ $proxy->can($method_to_call)};
452bac1b 392 });
393 }
394 }
395 }
396
397 return;
398}
399
98aae381 400# private methods to help delegation ...
401
452bac1b 402sub _canonicalize_handles {
403 my $self = shift;
404 my $handles = $self->handles;
c84f324f 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 }
452bac1b 424 }
425 else {
c84f324f 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 );
452bac1b 438 }
439}
440
441sub _find_delegate_metaclass {
442 my $self = shift;
98aae381 443 if (my $class = $self->_isa_metadata) {
452bac1b 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 }
98aae381 453 elsif (my $role = $self->_does_metadata) {
452bac1b 454 # our role will always have
455 # a meta method
98aae381 456 return $role->meta;
452bac1b 457 }
458 else {
459 confess "Cannot find delegate metaclass for attribute " . $self->name;
460 }
461}
462
463sub _get_delegate_method_list {
464 my $self = shift;
465 my $meta = $self->_find_delegate_metaclass;
466 if ($meta->isa('Class::MOP::Class')) {
093b12c2 467 return map { $_->{name} } # NOTE: !never! delegate &meta
468 grep { $_->{class} ne 'Moose::Object' && $_->{name} ne 'meta' }
452bac1b 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
c0e30cf5 4791;
480
481__END__
482
483=pod
484
485=head1 NAME
486
6ba6d68c 487Moose::Meta::Attribute - The Moose attribute metaclass
c0e30cf5 488
489=head1 DESCRIPTION
490
e522431d 491This is a subclass of L<Class::MOP::Attribute> with Moose specific
6ba6d68c 492extensions.
493
494For the most part, the only time you will ever encounter an
495instance of this class is if you are doing some serious deep
496introspection. To really understand this class, you need to refer
497to the L<Class::MOP::Attribute> documentation.
e522431d 498
c0e30cf5 499=head1 METHODS
500
6ba6d68c 501=head2 Overridden methods
502
503These methods override methods in L<Class::MOP::Attribute> and add
504Moose specific features. You can safely assume though that they
505will behave just as L<Class::MOP::Attribute> does.
506
c0e30cf5 507=over 4
508
509=item B<new>
510
d500266f 511=item B<initialize_instance_slot>
512
452bac1b 513=item B<install_accessors>
514
39b3bc94 515=item B<accessor_metaclass>
516
946289d1 517=item B<get_value>
518
519=item B<set_value>
520
bcbaa845 521 eval { $point->meta->get_attribute('x')->set_value($point, 'fourty-two') };
522 if($@) {
523 print "Oops: $@\n";
524 }
525
526I<Attribute (x) does not pass the type constraint (Int) with 'fourty-two'>
527
528Before setting the value, a check is made on the type constraint of
529the attribute, if it has one, to see if the value passes it. If the
530value fails to pass, the set operation dies with a L<Carp/confess>.
531
532Any coercion to convert values is done before checking the type constraint.
533
534To check a value against a type constraint before setting it, fetch the
535attribute instance using L<Moose::Meta::Attribute/find_attribute_by_name>,
536fetch the type_constraint from the attribute using L<Moose::Meta::Attribute/type_constraint>
537and call L<Moose::Meta::TypeConstraint/check>. See L<Moose::Cookbook::RecipeX>
538for an example.
539
a15dff8d 540=back
541
6ba6d68c 542=head2 Additional Moose features
543
8449e6e7 544Moose attributes support type-constraint checking, weak reference
6ba6d68c 545creation and type coercion.
546
a15dff8d 547=over 4
548
9e93dd19 549=item B<clone_and_inherit_options>
550
551This is to support the C<has '+foo'> feature, it clones an attribute
552from a superclass and allows a very specific set of changes to be made
553to the attribute.
554
a15dff8d 555=item B<has_type_constraint>
556
6ba6d68c 557Returns true if this meta-attribute has a type constraint.
558
a15dff8d 559=item B<type_constraint>
560
6ba6d68c 561A read-only accessor for this meta-attribute's type constraint. For
562more information on what you can do with this, see the documentation
563for L<Moose::Meta::TypeConstraint>.
a15dff8d 564
452bac1b 565=item B<has_handles>
566
567Returns true if this meta-attribute performs delegation.
568
569=item B<handles>
570
571This returns the value which was passed into the handles option.
572
6ba6d68c 573=item B<is_weak_ref>
a15dff8d 574
02a0fb52 575Returns true if this meta-attribute produces a weak reference.
4b598ea3 576
ca01a97b 577=item B<is_required>
578
02a0fb52 579Returns true if this meta-attribute is required to have a value.
ca01a97b 580
581=item B<is_lazy>
582
02a0fb52 583Returns true if this meta-attribute should be initialized lazily.
ca01a97b 584
585NOTE: lazy attributes, B<must> have a C<default> field set.
586
34a66aa3 587=item B<should_coerce>
4b598ea3 588
02a0fb52 589Returns true if this meta-attribute should perform type coercion.
6ba6d68c 590
536f0b17 591=item B<should_auto_deref>
592
593Returns true if this meta-attribute should perform automatic
594auto-dereferencing.
595
596NOTE: This can only be done for attributes whose type constraint is
597either I<ArrayRef> or I<HashRef>.
598
8c9d74e7 599=item B<has_trigger>
600
02a0fb52 601Returns true if this meta-attribute has a trigger set.
602
8c9d74e7 603=item B<trigger>
604
02a0fb52 605This is a CODE reference which will be executed every time the
606value of an attribute is assigned. The CODE ref will get two values,
607the invocant and the new value. This can be used to handle I<basic>
608bi-directional relations.
609
ddbdc0cb 610=item B<documentation>
611
612This is a string which contains the documentation for this attribute.
613It serves no direct purpose right now, but it might in the future
614in some kind of automated documentation system perhaps.
615
616=item B<has_documentation>
617
618Returns true if this meta-attribute has any documentation.
619
c0e30cf5 620=back
621
622=head1 BUGS
623
624All complex software has bugs lurking in it, and this module is no
625exception. If you find a bug please either email me, or add the bug
626to cpan-RT.
627
c0e30cf5 628=head1 AUTHOR
629
630Stevan Little E<lt>stevan@iinteractive.comE<gt>
631
98aae381 632Yuval Kogman E<lt>nothingmuch@woobling.comE<gt>
633
c0e30cf5 634=head1 COPYRIGHT AND LICENSE
635
b77fdbed 636Copyright 2006, 2007 by Infinity Interactive, Inc.
c0e30cf5 637
638L<http://www.iinteractive.com>
639
640This library is free software; you can redistribute it and/or modify
641it under the same terms as Perl itself.
642
8a7a9c53 643=cut