X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMoose%2FMeta%2FAttribute.pm;h=738900f3e31bfec795f65ca9182d8cbd60a1d1e8;hb=refs%2Ftags%2F0.96;hp=9b93b15ece29a8f089a0d597588ae97ae9ac96e8;hpb=be21cc5c8077f4bb88f1682793ab70f35e627e86;p=gitmo%2FMoose.git diff --git a/lib/Moose/Meta/Attribute.pm b/lib/Moose/Meta/Attribute.pm index 9b93b15..738900f 100644 --- a/lib/Moose/Meta/Attribute.pm +++ b/lib/Moose/Meta/Attribute.pm @@ -5,9 +5,11 @@ use strict; use warnings; use Scalar::Util 'blessed', 'weaken'; +use List::MoreUtils 'any'; +use Try::Tiny; use overload (); -our $VERSION = '0.76'; +our $VERSION = '0.96'; our $AUTHORITY = 'cpan:STEVAN'; use Moose::Meta::Method::Accessor; @@ -15,37 +17,8 @@ use Moose::Meta::Method::Delegation; use Moose::Util (); use Moose::Util::TypeConstraints (); -use base 'Class::MOP::Attribute'; - -# options which are not directly used -# but we store them for metadata purposes -__PACKAGE__->meta->add_attribute('isa' => (reader => '_isa_metadata')); -__PACKAGE__->meta->add_attribute('does' => (reader => '_does_metadata')); -__PACKAGE__->meta->add_attribute('is' => (reader => '_is_metadata')); - -# these are actual options for the attrs -__PACKAGE__->meta->add_attribute('required' => (reader => 'is_required' )); -__PACKAGE__->meta->add_attribute('lazy' => (reader => 'is_lazy' )); -__PACKAGE__->meta->add_attribute('lazy_build' => (reader => 'is_lazy_build' )); -__PACKAGE__->meta->add_attribute('coerce' => (reader => 'should_coerce' )); -__PACKAGE__->meta->add_attribute('weak_ref' => (reader => 'is_weak_ref' )); -__PACKAGE__->meta->add_attribute('auto_deref' => (reader => 'should_auto_deref')); -__PACKAGE__->meta->add_attribute('type_constraint' => ( - reader => 'type_constraint', - predicate => 'has_type_constraint', -)); -__PACKAGE__->meta->add_attribute('trigger' => ( - reader => 'trigger', - predicate => 'has_trigger', -)); -__PACKAGE__->meta->add_attribute('handles' => ( - reader => 'handles', - predicate => 'has_handles', -)); -__PACKAGE__->meta->add_attribute('documentation' => ( - reader => 'documentation', - predicate => 'has_documentation', -)); +use base 'Class::MOP::Attribute', 'Moose::Meta::Mixin::AttributeCore'; + __PACKAGE__->meta->add_attribute('traits' => ( reader => 'applied_traits', predicate => 'has_applied_traits', @@ -57,7 +30,7 @@ __PACKAGE__->meta->add_attribute('traits' => ( # for metatrait aliases. sub does { my ($self, $role_name) = @_; - my $name = eval { + my $name = try { Moose::Util::resolve_metatrait_alias(Attribute => $role_name) }; return 0 if !defined($name); # failed to load class @@ -77,28 +50,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); + my ( $new_class, @traits ) = $class->interpolate_class(\%args); - $new_class->new($name, @args, ( scalar(@traits) ? ( traits => \@traits ) : () ) ); + $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 +97,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++]; @@ -146,7 +136,7 @@ my @legal_options_for_inheritance = qw( documentation lazy handles builder type_constraint definition_context - lazy_build + lazy_build weak_ref ); sub legal_options_for_inheritance { @legal_options_for_inheritance } @@ -225,7 +215,7 @@ sub clone_and_inherit_options { # 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,7 +234,7 @@ 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 ); @@ -290,6 +280,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}); } @@ -297,7 +290,7 @@ sub _process_options { if (exists $options->{isa}) { if (exists $options->{does}) { - if (eval { $options->{isa}->can('does') }) { + if (try { $options->{isa}->can('does') }) { ($options->{isa}->does($options->{does})) || $class->throw_error("Cannot have an isa option and a does option if the isa does not do the does on attribute ($name)", data => $options); } @@ -348,7 +341,6 @@ 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}"; @@ -406,8 +398,10 @@ sub initialize_instance_slot { $val = $self->_coerce_and_verify( $val, $instance ); $self->set_initial_value($instance, $val); - $meta_instance->weaken_slot_value($instance, $self->name) - if ref $val && $self->is_weak_ref; + + if ( ref $val && $self->is_weak_ref ) { + $self->_weaken_value($instance); + } } sub _call_builder { @@ -473,22 +467,33 @@ sub set_value { $value = $self->_coerce_and_verify( $value, $instance ); - my $meta_instance = Class::MOP::Class->initialize(blessed($instance)) - ->get_meta_instance; + my @old; + if ( $self->has_trigger && $self->has_value($instance) ) { + @old = $self->get_value($instance, 'for trigger'); + } - $meta_instance->set_slot_value($instance, $attr_name, $value); + $self->SUPER::set_value($instance, $value); - if (ref $value && $self->is_weak_ref) { - $meta_instance->weaken_slot_value($instance, $attr_name); + if ( ref $value && $self->is_weak_ref ) { + $self->_weaken_value($instance); } if ($self->has_trigger) { - $self->trigger->($instance, $value); + $self->trigger->($instance, $value, @old); } } +sub _weaken_value { + my ( $self, $instance ) = @_; + + my $meta_instance = Class::MOP::Class->initialize( blessed($instance) ) + ->get_meta_instance; + + $meta_instance->weaken_slot_value( $instance, $self->name ); +} + sub get_value { - my ($self, $instance) = @_; + my ($self, $instance, $for_trigger) = @_; if ($self->is_lazy) { unless ($self->has_value($instance)) { @@ -505,7 +510,7 @@ sub get_value { } } - if ($self->should_auto_deref) { + if ( $self->should_auto_deref && ! $for_trigger ) { my $type_constraint = $self->type_constraint; @@ -541,6 +546,38 @@ sub install_accessors { return; } +sub _check_associated_methods { + my $self = shift; + unless ( + @{ $self->associated_methods } + || ($self->_is_metadata || '') eq 'bare' + ) { + Carp::cluck( + 'Attribute (' . $self->name . ') of class ' + . $self->associated_class->name + . ' has no associated methods' + . ' (did you mean to provide an "is" argument?)' + . "\n" + ) + } +} + +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 are overwriting a locally defined method ($accessor) with " + . "an accessor" + ); + } + $self->SUPER::_process_accessors(@_); +} + sub remove_accessors { my $self = shift; $self->SUPER::remove_accessors(@_); @@ -582,6 +619,7 @@ 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); } } @@ -590,6 +628,9 @@ sub remove_delegation { my %handles = $self->_canonicalize_handles; my $associated_class = $self->associated_class; foreach my $handle (keys %handles) { + next unless any { $handle eq $_ } + map { $_->name } + @{ $self->associated_methods }; $self->associated_class->remove_method($handle); } } @@ -615,20 +656,25 @@ 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 { - my $role_meta = Class::MOP::load_class($handles); + 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 its metaclass is not a Moose::Meta::Role", data => $handles); - return map { $_ => $_ } ( + return map { $_ => $_ } + grep { $_ ne 'meta' } ( $role_meta->get_method_list, - $role_meta->get_required_method_list - ); + map { $_->name } $role_meta->get_required_method_list, + ); } } @@ -669,16 +715,17 @@ sub delegation_metaclass { 'Moose::Meta::Method::Delegation' } sub _make_delegation_method { my ( $self, $handle_name, $method_to_call ) = @_; - my $method_body; + my @curried_arguments; - $method_body = $method_to_call - if 'CODE' eq ref($method_to_call); + ($method_to_call, @curried_arguments) = @$method_to_call + if 'ARRAY' eq ref($method_to_call); return $self->delegation_metaclass->new( name => $handle_name, package_name => $self->associated_class->name, attribute => $self, delegate_to_method => $method_to_call, + curried_arguments => \@curried_arguments, ); } @@ -761,7 +808,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 @@ -772,6 +819,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 @@ -910,6 +962,11 @@ for an example. This method overrides the parent to also install delegation methods. +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. @@ -1064,9 +1121,7 @@ Returns true if this attribute has any traits applied. =head1 BUGS -All complex software has bugs lurking in it, and this module is no -exception. If you find a bug please either email me, or add the bug -to cpan-RT. +See L for details on reporting bugs. =head1 AUTHOR @@ -1076,7 +1131,7 @@ Yuval Kogman Enothingmuch@woobling.comE =head1 COPYRIGHT AND LICENSE -Copyright 2006-2009 by Infinity Interactive, Inc. +Copyright 2006-2010 by Infinity Interactive, Inc. L