more type system hacking and tests
[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
18ea2bcc 11our $VERSION = '0.12';
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
8ac5969a 243 ? (blessed($val) && overload::Overloaded($val)
a909a4df 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)
8ac5969a 280 ? ("'" .
281 (blessed($value) && overload::Overloaded($value)
282 ? overload::StrVal($value)
283 : $value)
284 . "'")
a909a4df 285 : "undef")
946289d1 286 if defined($value);
287 }
288
289 my $meta_instance = Class::MOP::Class->initialize(blessed($instance))
290 ->get_meta_instance;
291
292 $meta_instance->set_slot_value($instance, $attr_name, $value);
293
294 if (ref $value && $self->is_weak_ref) {
295 $meta_instance->weaken_slot_value($instance, $attr_name);
296 }
297
298 if ($self->has_trigger) {
299 $self->trigger->($instance, $value, $self);
300 }
301}
302
303sub get_value {
304 my ($self, $instance) = @_;
305
306 if ($self->is_lazy) {
307 unless ($self->has_value($instance)) {
308 if ($self->has_default) {
309 my $default = $self->default($instance);
310 $self->set_value($instance, $default);
311 }
312 else {
313 $self->set_value($instance, undef);
314 }
315 }
316 }
317
318 if ($self->should_auto_deref) {
319
320 my $type_constraint = $self->type_constraint;
321
322 if ($type_constraint->is_a_type_of('ArrayRef')) {
323 my $rv = $self->SUPER::get_value($instance);
324 return unless defined $rv;
325 return wantarray ? @{ $rv } : $rv;
326 }
327 elsif ($type_constraint->is_a_type_of('HashRef')) {
328 my $rv = $self->SUPER::get_value($instance);
329 return unless defined $rv;
330 return wantarray ? %{ $rv } : $rv;
331 }
332 else {
333 confess "Can not auto de-reference the type constraint '" . $type_constraint->name . "'";
334 }
335
336 }
337 else {
338
339 return $self->SUPER::get_value($instance);
340 }
341}
a15dff8d 342
d617b644 343## installing accessors
c0e30cf5 344
d617b644 345sub accessor_metaclass { 'Moose::Meta::Method::Accessor' }
d7f17ebb 346
452bac1b 347sub install_accessors {
348 my $self = shift;
349 $self->SUPER::install_accessors(@_);
350
351 if ($self->has_handles) {
352
353 # NOTE:
354 # Here we canonicalize the 'handles' option
355 # this will sort out any details and always
356 # return an hash of methods which we want
357 # to delagate to, see that method for details
358 my %handles = $self->_canonicalize_handles();
359
360 # find the name of the accessor for this attribute
361 my $accessor_name = $self->reader || $self->accessor;
362 (defined $accessor_name)
363 || confess "You cannot install delegation without a reader or accessor for the attribute";
364
365 # make sure we handle HASH accessors correctly
366 ($accessor_name) = keys %{$accessor_name}
367 if ref($accessor_name) eq 'HASH';
368
369 # install the delegation ...
370 my $associated_class = $self->associated_class;
371 foreach my $handle (keys %handles) {
372 my $method_to_call = $handles{$handle};
373
374 (!$associated_class->has_method($handle))
375 || confess "You cannot overwrite a locally defined method ($handle) with a delegation";
376
d022f632 377 # NOTE:
378 # handles is not allowed to delegate
379 # any of these methods, as they will
380 # override the ones in your class, which
381 # is almost certainly not what you want.
382 next if $handle =~ /^BUILD|DEMOLISH$/ || Moose::Object->can($handle);
383
452bac1b 384 if ((reftype($method_to_call) || '') eq 'CODE') {
385 $associated_class->add_method($handle => $method_to_call);
386 }
387 else {
388 $associated_class->add_method($handle => sub {
b805c70c 389 # FIXME
390 # we should check for lack of
391 # a callable return value from
392 # the accessor here
cbe25729 393 my $proxy = (shift)->$accessor_name();
394 @_ = ($proxy, @_);
395 goto &{ $proxy->can($method_to_call)};
452bac1b 396 });
397 }
398 }
399 }
400
401 return;
402}
403
98aae381 404# private methods to help delegation ...
405
452bac1b 406sub _canonicalize_handles {
407 my $self = shift;
408 my $handles = $self->handles;
c84f324f 409 if (my $handle_type = ref($handles)) {
410 if ($handle_type eq 'HASH') {
411 return %{$handles};
412 }
413 elsif ($handle_type eq 'ARRAY') {
414 return map { $_ => $_ } @{$handles};
415 }
416 elsif ($handle_type eq 'Regexp') {
417 ($self->has_type_constraint)
418 || confess "Cannot delegate methods based on a RegExpr without a type constraint (isa)";
419 return map { ($_ => $_) }
420 grep { /$handles/ } $self->_get_delegate_method_list;
421 }
422 elsif ($handle_type eq 'CODE') {
423 return $handles->($self, $self->_find_delegate_metaclass);
424 }
425 else {
426 confess "Unable to canonicalize the 'handles' option with $handles";
427 }
452bac1b 428 }
429 else {
c84f324f 430 my $role_meta = eval { $handles->meta };
431 if ($@) {
432 confess "Unable to canonicalize the 'handles' option with $handles because : $@";
433 }
434
435 (blessed $role_meta && $role_meta->isa('Moose::Meta::Role'))
436 || confess "Unable to canonicalize the 'handles' option with $handles because ->meta is not a Moose::Meta::Role";
437
438 return map { $_ => $_ } (
439 $role_meta->get_method_list,
440 $role_meta->get_required_method_list
441 );
452bac1b 442 }
443}
444
445sub _find_delegate_metaclass {
446 my $self = shift;
98aae381 447 if (my $class = $self->_isa_metadata) {
452bac1b 448 # if the class does have
449 # a meta method, use it
450 return $class->meta if $class->can('meta');
451 # otherwise we might be
452 # dealing with a non-Moose
453 # class, and need to make
454 # our own metaclass
455 return Moose::Meta::Class->initialize($class);
456 }
98aae381 457 elsif (my $role = $self->_does_metadata) {
452bac1b 458 # our role will always have
459 # a meta method
98aae381 460 return $role->meta;
452bac1b 461 }
462 else {
463 confess "Cannot find delegate metaclass for attribute " . $self->name;
464 }
465}
466
467sub _get_delegate_method_list {
468 my $self = shift;
469 my $meta = $self->_find_delegate_metaclass;
470 if ($meta->isa('Class::MOP::Class')) {
093b12c2 471 return map { $_->{name} } # NOTE: !never! delegate &meta
472 grep { $_->{class} ne 'Moose::Object' && $_->{name} ne 'meta' }
452bac1b 473 $meta->compute_all_applicable_methods;
474 }
475 elsif ($meta->isa('Moose::Meta::Role')) {
476 return $meta->get_method_list;
477 }
478 else {
479 confess "Unable to recognize the delegate metaclass '$meta'";
480 }
481}
482
c0e30cf5 4831;
484
485__END__
486
487=pod
488
489=head1 NAME
490
6ba6d68c 491Moose::Meta::Attribute - The Moose attribute metaclass
c0e30cf5 492
493=head1 DESCRIPTION
494
e522431d 495This is a subclass of L<Class::MOP::Attribute> with Moose specific
6ba6d68c 496extensions.
497
498For the most part, the only time you will ever encounter an
499instance of this class is if you are doing some serious deep
500introspection. To really understand this class, you need to refer
501to the L<Class::MOP::Attribute> documentation.
e522431d 502
c0e30cf5 503=head1 METHODS
504
6ba6d68c 505=head2 Overridden methods
506
507These methods override methods in L<Class::MOP::Attribute> and add
508Moose specific features. You can safely assume though that they
509will behave just as L<Class::MOP::Attribute> does.
510
c0e30cf5 511=over 4
512
513=item B<new>
514
d500266f 515=item B<initialize_instance_slot>
516
452bac1b 517=item B<install_accessors>
518
39b3bc94 519=item B<accessor_metaclass>
520
946289d1 521=item B<get_value>
522
523=item B<set_value>
524
bcbaa845 525 eval { $point->meta->get_attribute('x')->set_value($point, 'fourty-two') };
526 if($@) {
527 print "Oops: $@\n";
528 }
529
530I<Attribute (x) does not pass the type constraint (Int) with 'fourty-two'>
531
532Before setting the value, a check is made on the type constraint of
533the attribute, if it has one, to see if the value passes it. If the
534value fails to pass, the set operation dies with a L<Carp/confess>.
535
536Any coercion to convert values is done before checking the type constraint.
537
538To check a value against a type constraint before setting it, fetch the
539attribute instance using L<Moose::Meta::Attribute/find_attribute_by_name>,
540fetch the type_constraint from the attribute using L<Moose::Meta::Attribute/type_constraint>
541and call L<Moose::Meta::TypeConstraint/check>. See L<Moose::Cookbook::RecipeX>
542for an example.
543
a15dff8d 544=back
545
6ba6d68c 546=head2 Additional Moose features
547
8449e6e7 548Moose attributes support type-constraint checking, weak reference
6ba6d68c 549creation and type coercion.
550
a15dff8d 551=over 4
552
9e93dd19 553=item B<clone_and_inherit_options>
554
555This is to support the C<has '+foo'> feature, it clones an attribute
556from a superclass and allows a very specific set of changes to be made
557to the attribute.
558
a15dff8d 559=item B<has_type_constraint>
560
6ba6d68c 561Returns true if this meta-attribute has a type constraint.
562
a15dff8d 563=item B<type_constraint>
564
6ba6d68c 565A read-only accessor for this meta-attribute's type constraint. For
566more information on what you can do with this, see the documentation
567for L<Moose::Meta::TypeConstraint>.
a15dff8d 568
452bac1b 569=item B<has_handles>
570
571Returns true if this meta-attribute performs delegation.
572
573=item B<handles>
574
575This returns the value which was passed into the handles option.
576
6ba6d68c 577=item B<is_weak_ref>
a15dff8d 578
02a0fb52 579Returns true if this meta-attribute produces a weak reference.
4b598ea3 580
ca01a97b 581=item B<is_required>
582
02a0fb52 583Returns true if this meta-attribute is required to have a value.
ca01a97b 584
585=item B<is_lazy>
586
02a0fb52 587Returns true if this meta-attribute should be initialized lazily.
ca01a97b 588
589NOTE: lazy attributes, B<must> have a C<default> field set.
590
34a66aa3 591=item B<should_coerce>
4b598ea3 592
02a0fb52 593Returns true if this meta-attribute should perform type coercion.
6ba6d68c 594
536f0b17 595=item B<should_auto_deref>
596
597Returns true if this meta-attribute should perform automatic
598auto-dereferencing.
599
600NOTE: This can only be done for attributes whose type constraint is
601either I<ArrayRef> or I<HashRef>.
602
8c9d74e7 603=item B<has_trigger>
604
02a0fb52 605Returns true if this meta-attribute has a trigger set.
606
8c9d74e7 607=item B<trigger>
608
02a0fb52 609This is a CODE reference which will be executed every time the
610value of an attribute is assigned. The CODE ref will get two values,
611the invocant and the new value. This can be used to handle I<basic>
612bi-directional relations.
613
ddbdc0cb 614=item B<documentation>
615
616This is a string which contains the documentation for this attribute.
617It serves no direct purpose right now, but it might in the future
618in some kind of automated documentation system perhaps.
619
620=item B<has_documentation>
621
622Returns true if this meta-attribute has any documentation.
623
c0e30cf5 624=back
625
626=head1 BUGS
627
628All complex software has bugs lurking in it, and this module is no
629exception. If you find a bug please either email me, or add the bug
630to cpan-RT.
631
c0e30cf5 632=head1 AUTHOR
633
634Stevan Little E<lt>stevan@iinteractive.comE<gt>
635
98aae381 636Yuval Kogman E<lt>nothingmuch@woobling.comE<gt>
637
c0e30cf5 638=head1 COPYRIGHT AND LICENSE
639
b77fdbed 640Copyright 2006, 2007 by Infinity Interactive, Inc.
c0e30cf5 641
642L<http://www.iinteractive.com>
643
644This library is free software; you can redistribute it and/or modify
645it under the same terms as Perl itself.
646
8a7a9c53 647=cut