bump version to 0.91
[gitmo/Moose.git] / lib / Moose / Meta / Attribute.pm
index 8902f93..0ebc1b8 100644 (file)
@@ -7,7 +7,7 @@ use warnings;
 use Scalar::Util 'blessed', 'weaken';
 use overload     ();
 
-our $VERSION   = '0.74';
+our $VERSION   = '0.91';
 our $AUTHORITY = 'cpan:STEVAN';
 
 use Moose::Meta::Method::Accessor;
@@ -40,6 +40,7 @@ __PACKAGE__->meta->add_attribute('trigger' => (
 ));
 __PACKAGE__->meta->add_attribute('handles' => (
     reader    => 'handles',
+    writer    => '_set_handles',
     predicate => 'has_handles',
 ));
 __PACKAGE__->meta->add_attribute('documentation' => (
@@ -51,8 +52,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 +78,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 +125,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,53 +160,53 @@ 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
+    lazy_build weak_ref
 );
 
 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 +222,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 +236,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,7 +262,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 );
 
@@ -276,7 +294,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 +308,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 +369,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 +400,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 +414,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;
@@ -431,8 +451,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 {
@@ -454,7 +474,7 @@ sub _set_initial_slot_value {
 
         $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
@@ -476,6 +496,11 @@ sub set_value {
     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);
 
     if (ref $value && $self->is_weak_ref) {
@@ -483,12 +508,12 @@ sub set_value {
     }
 
     if ($self->has_trigger) {
-        $self->trigger->($instance, $value);
+        $self->trigger->($instance, $value, @old);
     }
 }
 
 sub get_value {
-    my ($self, $instance) = @_;
+    my ($self, $instance, $for_trigger) = @_;
 
     if ($self->is_lazy) {
         unless ($self->has_value($instance)) {
@@ -505,7 +530,7 @@ sub get_value {
         }
     }
 
-    if ($self->should_auto_deref) {
+    if ( $self->should_auto_deref && ! $for_trigger ) {
 
         my $type_constraint = $self->type_constraint;
 
@@ -541,6 +566,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,7 +639,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 {
@@ -615,20 +673,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,
+            );
     }
 }
 
@@ -674,11 +737,17 @@ sub _make_delegation_method {
     $method_body = $method_to_call
         if 'CODE' eq ref($method_to_call);
 
+    my @curried_arguments;
+
+    ($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 +830,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<reader>, C<writer>, or
 C<accessor> names. If the attribute is read-only ('ro') then it will
@@ -772,6 +841,11 @@ with the same name. If you provide an explicit C<writer> for a
 read-write attribute, then you will have a C<reader> with the same
 name as the attribute, and a C<writer> 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 +984,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.