X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMoose%2FMeta%2FAttribute.pm;h=59ec38580f83c68ae94d5cfb8a0773faea996844;hb=9e4ed568f2e5c1041e6fea8d0cbde420562ab5df;hp=c88dbae051f5de61aafa56db381e2ae42ee7b6e2;hpb=93a708fdd3c0fb148b8c05f272e6e43c8be46647;p=gitmo%2FMoose.git diff --git a/lib/Moose/Meta/Attribute.pm b/lib/Moose/Meta/Attribute.pm index c88dbae..59ec385 100644 --- a/lib/Moose/Meta/Attribute.pm +++ b/lib/Moose/Meta/Attribute.pm @@ -7,7 +7,7 @@ use warnings; use Scalar::Util 'blessed', 'weaken'; use overload (); -our $VERSION = '0.72'; +our $VERSION = '0.84'; our $AUTHORITY = 'cpan:STEVAN'; use Moose::Meta::Method::Accessor; @@ -51,8 +51,8 @@ __PACKAGE__->meta->add_attribute('traits' => ( predicate => 'has_applied_traits', )); -# we need to have a ->does method in here to -# more easily support traits, and the introspection +# we need to have a ->does method in here to +# more easily support traits, and the introspection # of those traits. We extend the does check to look # for metatrait aliases. sub does { @@ -77,28 +77,45 @@ sub throw_error { sub new { my ($class, $name, %options) = @_; $class->_process_options($name, \%options) unless $options{__hack_no_process_options}; # used from clone()... YECHKKK FIXME ICKY YUCK GROSS + + delete $options{__hack_no_process_options}; + + my %attrs = + ( map { $_ => 1 } + grep { defined } + map { $_->init_arg() } + $class->meta()->get_all_attributes() + ); + + my @bad = sort grep { ! $attrs{$_} } keys %options; + + if (@bad) + { + Carp::cluck "Found unknown argument(s) passed to '$name' attribute constructor in '$class': @bad"; + } + return $class->SUPER::new($name, %options); } sub interpolate_class_and_new { - my ($class, $name, @args) = @_; + my ($class, $name, %args) = @_; - my ( $new_class, @traits ) = $class->interpolate_class(@args); - - $new_class->new($name, @args, ( scalar(@traits) ? ( traits => \@traits ) : () ) ); + my ( $new_class, @traits ) = $class->interpolate_class(\%args); + + $new_class->new($name, %args, ( scalar(@traits) ? ( traits => \@traits ) : () ) ); } sub interpolate_class { - my ($class, %options) = @_; + my ($class, $options) = @_; $class = ref($class) || $class; - if ( my $metaclass_name = delete $options{metaclass} ) { + if ( my $metaclass_name = delete $options->{metaclass} ) { my $new_class = Moose::Util::resolve_metaclass_alias( Attribute => $metaclass_name ); - + if ( $class ne $new_class ) { if ( $new_class->can("interpolate_class") ) { - return $new_class->interpolate_class(%options); + return $new_class->interpolate_class($options); } else { $class = $new_class; } @@ -107,7 +124,7 @@ sub interpolate_class { my @traits; - if (my $traits = $options{traits}) { + if (my $traits = $options->{traits}) { my $i = 0; while ($i < @$traits) { my $trait = $traits->[$i++]; @@ -142,8 +159,8 @@ sub interpolate_class { # ... my @legal_options_for_inheritance = qw( - default coerce required - documentation lazy handles + default coerce required + documentation lazy handles builder type_constraint definition_context lazy_build @@ -152,43 +169,43 @@ my @legal_options_for_inheritance = qw( sub legal_options_for_inheritance { @legal_options_for_inheritance } # NOTE/TODO -# This method *must* be able to handle -# Class::MOP::Attribute instances as -# well. Yes, I know that is wrong, but -# apparently we didn't realize it was -# doing that and now we have some code -# which is dependent on it. The real -# solution of course is to push this +# This method *must* be able to handle +# Class::MOP::Attribute instances as +# well. Yes, I know that is wrong, but +# apparently we didn't realize it was +# doing that and now we have some code +# which is dependent on it. The real +# solution of course is to push this # feature back up into Class::MOP::Attribute # but I not right now, I am too lazy. -# However if you are reading this and -# looking for something to do,.. please +# However if you are reading this and +# looking for something to do,.. please # be my guest. # - stevan sub clone_and_inherit_options { my ($self, %options) = @_; - + my %copy = %options; - + my %actual_options; - + # NOTE: # we may want to extends a Class::MOP::Attribute - # in which case we need to be able to use the - # core set of legal options that have always + # in which case we need to be able to use the + # core set of legal options that have always # been here. But we allows Moose::Meta::Attribute # instances to changes them. # - SL my @legal_options = $self->can('legal_options_for_inheritance') ? $self->legal_options_for_inheritance : @legal_options_for_inheritance; - + foreach my $legal_option (@legal_options) { if (exists $options{$legal_option}) { $actual_options{$legal_option} = $options{$legal_option}; delete $options{$legal_option}; } - } + } if ($options{isa}) { my $type_constraint; @@ -204,7 +221,7 @@ sub clone_and_inherit_options { $actual_options{type_constraint} = $type_constraint; delete $options{isa}; } - + if ($options{does}) { my $type_constraint; if (blessed($options{does}) && $options{does}->isa('Moose::Meta::TypeConstraint')) { @@ -218,14 +235,14 @@ sub clone_and_inherit_options { $actual_options{type_constraint} = $type_constraint; delete $options{does}; - } + } # NOTE: - # this doesn't apply to Class::MOP::Attributes, + # this doesn't apply to Class::MOP::Attributes, # so we can ignore it for them. # - SL if ($self->can('interpolate_class')) { - ( $actual_options{metaclass}, my @traits ) = $self->interpolate_class(%options); + ( $actual_options{metaclass}, my @traits ) = $self->interpolate_class(\%options); my %seen; my @all_traits = grep { $seen{$_}++ } @{ $self->applied_traits || [] }, @traits; @@ -244,11 +261,11 @@ sub clone_and_inherit_options { sub clone { my ( $self, %params ) = @_; - my $class = $params{metaclass} || ref $self; + my $class = delete $params{metaclass} || ref $self; my ( @init, @non_init ); - foreach my $attr ( grep { $_->has_value($self) } $self->meta->compute_all_applicable_attributes ) { + foreach my $attr ( grep { $_->has_value($self) } Class::MOP::class_of($self)->get_all_attributes ) { push @{ $attr->has_init_arg ? \@init : \@non_init }, $attr; } @@ -276,7 +293,7 @@ sub _process_options { ## is => rw, accessor => _foo # turns into (accessor => _foo) ## is => ro, accessor => _foo # error, accesor is rw ### ------------------------- - + if ($options->{is} eq 'ro') { $class->throw_error("Cannot define an accessor name on a read-only attribute, accessors are read/write", data => $options) if exists $options->{accessor}; @@ -290,6 +307,9 @@ sub _process_options { $options->{accessor} ||= $name; } } + elsif ($options->{is} eq 'bare') { + # do nothing, but don't complain (later) about missing methods + } else { $class->throw_error("I do not understand this option (is => " . $options->{is} . ") on attribute ($name)", data => $options->{is}); } @@ -348,12 +368,11 @@ sub _process_options { $class->throw_error("You can not use lazy_build and default for the same attribute ($name)", data => $options) if exists $options->{default}; $options->{lazy} = 1; - $options->{required} = 1; $options->{builder} ||= "_build_${name}"; if ($name =~ /^_/) { $options->{clearer} ||= "_clear${name}"; $options->{predicate} ||= "_has${name}"; - } + } else { $options->{clearer} ||= "clear_${name}"; $options->{predicate} ||= "has_${name}"; @@ -380,7 +399,7 @@ sub initialize_instance_slot { my $value_is_set; if ( defined($init_arg) and exists $params->{$init_arg}) { $val = $params->{$init_arg}; - $value_is_set = 1; + $value_is_set = 1; } else { # skip it if it's lazy @@ -394,7 +413,7 @@ sub initialize_instance_slot { if ($self->has_default) { $val = $self->default($instance); $value_is_set = 1; - } + } elsif ($self->has_builder) { $val = $self->_call_builder($instance); $value_is_set = 1; @@ -403,13 +422,7 @@ sub initialize_instance_slot { return unless $value_is_set; - if ($self->has_type_constraint) { - my $type_constraint = $self->type_constraint; - if ($self->should_coerce && $type_constraint->has_coercion) { - $val = $type_constraint->coerce($val); - } - $self->verify_against_type_constraint($val, instance => $instance); - } + $val = $self->_coerce_and_verify( $val, $instance ); $self->set_initial_value($instance, $val); $meta_instance->weaken_slot_value($instance, $self->name) @@ -437,8 +450,8 @@ sub _call_builder { ## Slot management # FIXME: -# this duplicates too much code from -# Class::MOP::Attribute, we need to +# this duplicates too much code from +# Class::MOP::Attribute, we need to # refactor these bits eventually. # - SL sub _set_initial_slot_value { @@ -456,15 +469,11 @@ sub _set_initial_slot_value { } my $callback = sub { - my $val = shift; - if ($type_constraint) { - $val = $type_constraint->coerce($val) - if $can_coerce; - $self->verify_against_type_constraint($val, object => $instance); - } + my $val = $self->_coerce_and_verify( shift, $instance );; + $meta_instance->set_slot_value($instance, $slot_name, $val); }; - + my $initializer = $self->initializer; # most things will just want to set a value, so make it first arg @@ -481,19 +490,7 @@ sub set_value { $self->throw_error("Attribute ($attr_name) is required", object => $instance); } - if ($self->has_type_constraint) { - - my $type_constraint = $self->type_constraint; - - if ($self->should_coerce) { - $value = $type_constraint->coerce($value); - } - $type_constraint->_compiled_type_constraint->($value) - || $self->throw_error("Attribute (" - . $self->name - . ") does not pass the type constraint because " - . $type_constraint->get_message($value), object => $instance, data => $value); - } + $value = $self->_coerce_and_verify( $value, $instance ); my $meta_instance = Class::MOP::Class->initialize(blessed($instance)) ->get_meta_instance; @@ -520,12 +517,9 @@ sub get_value { } elsif ( $self->has_builder ) { $value = $self->_call_builder($instance); } - if ($self->has_type_constraint) { - my $type_constraint = $self->type_constraint; - $value = $type_constraint->coerce($value) - if ($self->should_coerce); - $self->verify_against_type_constraint($value); - } + + $value = $self->_coerce_and_verify( $value, $instance ); + $self->set_initial_value($instance, $value); } } @@ -563,9 +557,35 @@ sub install_accessors { my $self = shift; $self->SUPER::install_accessors(@_); $self->install_delegation if $self->has_handles; + unless ( + @{ $self->associated_methods } + || ($self->_is_metadata || '') eq 'bare' + ) { + Carp::cluck( + 'Attribute (' . $self->name . ') has no associated methods' + . ' (did you mean to provide an "is" argument?)' + . "\n" + ) + } return; } +sub _process_accessors { + my $self = shift; + my ($type, $accessor, $generate_as_inline_methods) = @_; + $accessor = (keys %$accessor)[0] if (ref($accessor)||'') eq 'HASH'; + my $method = $self->associated_class->get_method($accessor); + if ($method && !$method->isa('Class::MOP::Method::Accessor') + && (!$self->definition_context + || $method->package_name eq $self->definition_context->{package})) { + Carp::cluck( + "You cannot overwrite a locally defined method ($accessor) with " + . "an accessor" + ); + } + $self->SUPER::_process_accessors(@_); +} + sub remove_accessors { my $self = shift; $self->SUPER::remove_accessors(@_); @@ -607,7 +627,8 @@ sub install_delegation { my $method = $self->_make_delegation_method($handle, $method_to_call); $self->associated_class->add_method($method->name, $method); - } + $self->associate_method($method); + } } sub remove_delegation { @@ -640,25 +661,23 @@ sub _canonicalize_handles { elsif ($handle_type eq 'CODE') { return $handles->($self, $self->_find_delegate_metaclass); } + elsif (blessed($handles) && $handles->isa('Moose::Meta::TypeConstraint::DuckType')) { + return map { $_ => $_ } @{ $handles->methods }; + } else { $self->throw_error("Unable to canonicalize the 'handles' option with $handles", data => $handles); } } else { - Class::MOP::load_class($handles) - unless Class::MOP::is_class_loaded($handles); - - my $role_meta = eval { $handles->meta }; - if ($@) { - $self->throw_error("Unable to canonicalize the 'handles' option with $handles because : $@", data => $handles, error => $@); - } + Class::MOP::load_class($handles); + my $role_meta = Class::MOP::class_of($handles); (blessed $role_meta && $role_meta->isa('Moose::Meta::Role')) - || $self->throw_error("Unable to canonicalize the 'handles' option with $handles because ->meta is not a Moose::Meta::Role", data => $handles); - + || $self->throw_error("Unable to canonicalize the 'handles' option with $handles because its metaclass is not a Moose::Meta::Role", data => $handles); + return map { $_ => $_ } ( $role_meta->get_method_list, - $role_meta->get_required_method_list + map { $_->name } $role_meta->get_required_method_list, ); } } @@ -666,19 +685,13 @@ sub _canonicalize_handles { sub _find_delegate_metaclass { my $self = shift; if (my $class = $self->_isa_metadata) { - # if the class does have - # a meta method, use it - return $class->meta if $class->can('meta'); - # otherwise we might be - # dealing with a non-Moose - # class, and need to make - # our own metaclass + # we might be dealing with a non-Moose class, + # and need to make our own metaclass. if there's + # already a metaclass, it will be returned return Moose::Meta::Class->initialize($class); } elsif (my $role = $self->_does_metadata) { - # our role will always have - # a meta method - return $role->meta; + return Class::MOP::class_of($role); } else { $self->throw_error("Cannot find delegate metaclass for attribute " . $self->name); @@ -719,6 +732,23 @@ sub _make_delegation_method { ); } +sub _coerce_and_verify { + my $self = shift; + my $val = shift; + my $instance = shift; + + return $val unless $self->has_type_constraint; + + my $type_constraint = $self->type_constraint; + if ($self->should_coerce && $type_constraint->has_coercion) { + $val = $type_constraint->coerce($val); + } + + $self->verify_against_type_constraint($val, instance => $instance); + + return $val; +} + sub verify_against_type_constraint { my $self = shift; my $val = shift; @@ -752,10 +782,14 @@ Moose::Meta::Attribute - The Moose attribute metaclass This class is a subclass of L that provides additional Moose-specific functionality. -To really understand this class, you will probably need to start with -the L documentation. This class can be -understood as a set of additional features on top of the basic feature -provided by that parent class. +To really understand this class, you will need to start with the +L documentation. This class can be understood +as a set of additional features on top of the basic feature provided +by that parent class. + +=head1 INHERITANCE + +C is a subclass of L. =head1 METHODS @@ -777,7 +811,7 @@ It adds the following options to the constructor: =over 8 -=item * is => 'ro' or 'rw' +=item * is => 'ro', 'rw', 'bare' This provides a shorthand for specifying the C, C, or C names. If the attribute is read-only ('ro') then it will @@ -788,6 +822,11 @@ with the same name. If you provide an explicit C for a read-write attribute, then you will have a C with the same name as the attribute, and a C with the name you provided. +Use 'bare' when you are deliberately not installing any methods +(accessor, reader, etc.) associated with this attribute; otherwise, +Moose will issue a deprecation warning when this attribute is added to a +metaclass. + =item * isa => $type This option accepts a type. The type can be a string, which should be @@ -822,7 +861,7 @@ the attribute is set. An attribute which is required must be provided to the constructor. An attribute which is required can also have a C or C, -which will satisy its required-ness. +which will satisfy its required-ness. A required attribute must have a C, C or a non-C C @@ -879,8 +918,12 @@ supply a C option to provide a new name for the attribute. The C<%options> can only specify options handled by L. +=back + =head2 Value management +=over 4 + =item B<< $attr->initialize_instance_slot($meta_instance, $instance, $params) >> This method is used internally to initialize the attribute's slot in @@ -922,7 +965,12 @@ for an example. This method overrides the parent to also install delegation methods. -=item B<< $attr->remove_accessors>> +If, after installing all methods, the attribute object has no associated +methods, it throws an error unless C<< is => 'bare' >> was passed to the +attribute constructor. (Trying to add an attribute that has no associated +methods is almost always an error.) + +=item B<< $attr->remove_accessors >> This method overrides the parent to also remove delegation methods. @@ -953,10 +1001,12 @@ L. These methods are not found in the superclass. They support features provided by Moose. +=over 4 + =item B<< $attr->does($role) >> This indicates whether the I does the given -role. The role can be given as a full class name, or as a resolveable +role. The role can be given as a full class name, or as a resolvable trait name. Note that this checks the attribute itself, not its type constraint, @@ -969,7 +1019,7 @@ This is an alternate constructor that handles the C and C options. Effectively, this method is a factory that finds or creates the -appropriate class for the given C and/or C and/or C. Once it has the appropriate class, it will call C<< $class->new($name, %options) >> on that class. @@ -1048,7 +1098,7 @@ true. This is the subroutine reference that was in the C option passed to the constructor, if any. -=item B<< $attr->has_trigger>> +=item B<< $attr->has_trigger >> Returns true if this attribute has a trigger set.