X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMoose%2FMeta%2FAttribute.pm;h=e59ebaf0122e74d6158b0098db5a04d859213757;hb=a486d5ade76c569625615022b49d03c92d6e93c2;hp=e8d5afc3f911e1eb1f4ef6db7fe443a76427f3c0;hpb=d4048ef33f6cad8a3453766505ee0c67690796f6;p=gitmo%2FMoose.git diff --git a/lib/Moose/Meta/Attribute.pm b/lib/Moose/Meta/Attribute.pm index e8d5afc..e59ebaf 100644 --- a/lib/Moose/Meta/Attribute.pm +++ b/lib/Moose/Meta/Attribute.pm @@ -4,21 +4,26 @@ package Moose::Meta::Attribute; use strict; use warnings; +use Class::MOP (); use Scalar::Util 'blessed', 'weaken'; use List::MoreUtils 'any'; use Try::Tiny; use overload (); -our $VERSION = '0.93'; +our $VERSION = '1.19'; our $AUTHORITY = 'cpan:STEVAN'; +use Moose::Deprecated; use Moose::Meta::Method::Accessor; use Moose::Meta::Method::Delegation; use Moose::Util (); use Moose::Util::TypeConstraints (); +use Class::MOP::MiniTrait; use base 'Class::MOP::Attribute', 'Moose::Meta::Mixin::AttributeCore'; +Class::MOP::MiniTrait::apply(__PACKAGE__, 'Moose::Meta::Object::Trait'); + __PACKAGE__->meta->add_attribute('traits' => ( reader => 'applied_traits', predicate => 'has_applied_traits', @@ -131,15 +136,10 @@ sub interpolate_class { # ... -my @legal_options_for_inheritance = qw( - default coerce required - documentation lazy handles - builder type_constraint - definition_context - lazy_build weak_ref -); - -sub legal_options_for_inheritance { @legal_options_for_inheritance } +# method-generating options shouldn't be overridden +sub illegal_options_for_inheritance { + qw(reader writer accessor clearer predicate) +} # NOTE/TODO # This method *must* be able to handle @@ -158,10 +158,6 @@ sub legal_options_for_inheritance { @legal_options_for_inheritance } 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 @@ -169,16 +165,13 @@ sub clone_and_inherit_options { # 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}; - } - } + my @illegal_options = $self->can('illegal_options_for_inheritance') + ? $self->illegal_options_for_inheritance + : (); + + my @found_illegal_options = grep { exists $options{$_} && exists $self->{$_} ? $_ : undef } @illegal_options; + (scalar @found_illegal_options == 0) + || $self->throw_error("Illegal inherited options => (" . (join ', ' => @found_illegal_options) . ")", data => \%options); if ($options{isa}) { my $type_constraint; @@ -191,8 +184,7 @@ sub clone_and_inherit_options { || $self->throw_error("Could not find the type constraint '" . $options{isa} . "'", data => $options{isa}); } - $actual_options{type_constraint} = $type_constraint; - delete $options{isa}; + $options{type_constraint} = $type_constraint; } if ($options{does}) { @@ -206,8 +198,7 @@ sub clone_and_inherit_options { || $self->throw_error("Could not find the type constraint '" . $options{does} . "'", data => $options{does}); } - $actual_options{type_constraint} = $type_constraint; - delete $options{does}; + $options{type_constraint} = $type_constraint; } # NOTE: @@ -215,20 +206,19 @@ 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); + ( $options{metaclass}, my @traits ) = $self->interpolate_class(\%options); my %seen; my @all_traits = grep { $seen{$_}++ } @{ $self->applied_traits || [] }, @traits; - $actual_options{traits} = \@all_traits if @all_traits; - - delete @options{qw(metaclass traits)}; + $options{traits} = \@all_traits if @all_traits; } - (scalar keys %options == 0) - || $self->throw_error("Illegal inherited options => (" . (join ', ' => keys %options) . ")", data => \%options); - + # This method can be called on a CMOP::Attribute object, so we need to + # make sure we can call this method. + $self->_process_lazy_build_option( $self->name, \%options ) + if $self->can('_process_lazy_build_option'); - $self->clone(%actual_options); + $self->clone(%options); } sub clone { @@ -256,111 +246,206 @@ sub clone { } sub _process_options { - my ($class, $name, $options) = @_; + my ( $class, $name, $options ) = @_; + + $class->_process_is_option( $name, $options ); + $class->_process_isa_option( $name, $options ); + $class->_process_does_option( $name, $options ); + $class->_process_coerce_option( $name, $options ); + $class->_process_trigger_option( $name, $options ); + $class->_process_auto_deref_option( $name, $options ); + $class->_process_lazy_build_option( $name, $options ); + $class->_process_lazy_option( $name, $options ); + $class->_process_required_option( $name, $options ); +} + +sub _process_is_option { + my ( $class, $name, $options ) = @_; - if (exists $options->{is}) { + return unless $options->{is}; - ### ------------------------- - ## is => ro, writer => _foo # turns into (reader => foo, writer => _foo) as before - ## is => rw, writer => _foo # turns into (reader => foo, writer => _foo) - ## is => rw, accessor => _foo # turns into (accessor => _foo) - ## is => ro, accessor => _foo # error, accesor is rw - ### ------------------------- + ### ------------------------- + ## is => ro, writer => _foo # turns into (reader => foo, writer => _foo) as before + ## is => rw, writer => _foo # turns into (reader => foo, writer => _foo) + ## 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}; + 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}; + $options->{reader} ||= $name; + } + elsif ( $options->{is} eq 'rw' ) { + if ( $options->{writer} ) { $options->{reader} ||= $name; } - elsif ($options->{is} eq 'rw') { - if ($options->{writer}) { - $options->{reader} ||= $name; - } - else { - $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}); + $options->{accessor} ||= $name; } } + elsif ( $options->{is} eq 'bare' ) { + return; + # 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} ); + } +} - if (exists $options->{isa}) { - if (exists $options->{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); - } - else { - $class->throw_error("Cannot have an isa option which cannot ->does() on attribute ($name)", data => $options); - } - } +sub _process_isa_option { + my ( $class, $name, $options ) = @_; - # allow for anon-subtypes here ... - if (blessed($options->{isa}) && $options->{isa}->isa('Moose::Meta::TypeConstraint')) { - $options->{type_constraint} = $options->{isa}; - } - else { - $options->{type_constraint} = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($options->{isa}); - } - } - elsif (exists $options->{does}) { - # allow for anon-subtypes here ... - if (blessed($options->{does}) && $options->{does}->isa('Moose::Meta::TypeConstraint')) { - $options->{type_constraint} = $options->{does}; + return unless exists $options->{isa}; + + if ( exists $options->{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 ); } else { - $options->{type_constraint} = Moose::Util::TypeConstraints::find_or_create_does_type_constraint($options->{does}); + $class->throw_error( + "Cannot have an isa option which cannot ->does() on attribute ($name)", + data => $options ); } } - if (exists $options->{coerce} && $options->{coerce}) { - (exists $options->{type_constraint}) - || $class->throw_error("You cannot have coercion without specifying a type constraint on attribute ($name)", data => $options); - $class->throw_error("You cannot have a weak reference to a coerced value on attribute ($name)", data => $options) - if $options->{weak_ref}; + # allow for anon-subtypes here ... + if ( blessed( $options->{isa} ) + && $options->{isa}->isa('Moose::Meta::TypeConstraint') ) { + $options->{type_constraint} = $options->{isa}; } - - if (exists $options->{trigger}) { - ('CODE' eq ref $options->{trigger}) - || $class->throw_error("Trigger must be a CODE ref on attribute ($name)", data => $options->{trigger}); + else { + $options->{type_constraint} + = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint( + $options->{isa} ); } +} - if (exists $options->{auto_deref} && $options->{auto_deref}) { - (exists $options->{type_constraint}) - || $class->throw_error("You cannot auto-dereference without specifying a type constraint on attribute ($name)", data => $options); - ($options->{type_constraint}->is_a_type_of('ArrayRef') || - $options->{type_constraint}->is_a_type_of('HashRef')) - || $class->throw_error("You cannot auto-dereference anything other than a ArrayRef or HashRef on attribute ($name)", data => $options); - } +sub _process_does_option { + my ( $class, $name, $options ) = @_; - if (exists $options->{lazy_build} && $options->{lazy_build} == 1) { - $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->{builder} ||= "_build_${name}"; - if ($name =~ /^_/) { - $options->{clearer} ||= "_clear${name}"; - $options->{predicate} ||= "_has${name}"; - } - else { - $options->{clearer} ||= "clear_${name}"; - $options->{predicate} ||= "has_${name}"; - } + return unless exists $options->{does} && ! exists $options->{isa}; + + # allow for anon-subtypes here ... + if ( blessed( $options->{does} ) + && $options->{does}->isa('Moose::Meta::TypeConstraint') ) { + $options->{type_constraint} = $options->{does}; } + else { + $options->{type_constraint} + = Moose::Util::TypeConstraints::find_or_create_does_type_constraint( + $options->{does} ); + } +} + +sub _process_coerce_option { + my ( $class, $name, $options ) = @_; + + return unless $options->{coerce}; - if (exists $options->{lazy} && $options->{lazy}) { - (exists $options->{default} || defined $options->{builder} ) - || $class->throw_error("You cannot have lazy attribute ($name) without specifying a default value for it", data => $options); + ( exists $options->{type_constraint} ) + || $class->throw_error( + "You cannot have coercion without specifying a type constraint on attribute ($name)", + data => $options ); + + $class->throw_error( + "You cannot have a weak reference to a coerced value on attribute ($name)", + data => $options ) + if $options->{weak_ref}; + + unless ( $options->{type_constraint}->has_coercion ) { + my $type = $options->{type_constraint}->name; + + Moose::Deprecated::deprecated( + feature => 'coerce without coercion', + message => + "You cannot coerce an attribute ($name) unless its type ($type) has a coercion" + ); } +} + +sub _process_trigger_option { + my ( $class, $name, $options ) = @_; + + return unless exists $options->{trigger}; + + ( 'CODE' eq ref $options->{trigger} ) + || $class->throw_error("Trigger must be a CODE ref on attribute ($name)", data => $options->{trigger}); +} + +sub _process_auto_deref_option { + my ( $class, $name, $options ) = @_; + + return unless $options->{auto_deref}; + + ( exists $options->{type_constraint} ) + || $class->throw_error( + "You cannot auto-dereference without specifying a type constraint on attribute ($name)", + data => $options ); + + ( $options->{type_constraint}->is_a_type_of('ArrayRef') + || $options->{type_constraint}->is_a_type_of('HashRef') ) + || $class->throw_error( + "You cannot auto-dereference anything other than a ArrayRef or HashRef on attribute ($name)", + data => $options ); +} - if ( $options->{required} && !( ( !exists $options->{init_arg} || defined $options->{init_arg} ) || exists $options->{default} || defined $options->{builder} ) ) { - $class->throw_error("You cannot have a required attribute ($name) without a default, builder, or an init_arg", data => $options); +sub _process_lazy_build_option { + my ( $class, $name, $options ) = @_; + + return unless $options->{lazy_build}; + + $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->{builder} ||= "_build_${name}"; + + if ( $name =~ /^_/ ) { + $options->{clearer} ||= "_clear${name}"; + $options->{predicate} ||= "_has${name}"; } + else { + $options->{clearer} ||= "clear_${name}"; + $options->{predicate} ||= "has_${name}"; + } +} + +sub _process_lazy_option { + my ( $class, $name, $options ) = @_; + + return unless $options->{lazy}; + ( exists $options->{default} || defined $options->{builder} ) + || $class->throw_error( + "You cannot have a lazy attribute ($name) without specifying a default value for it", + data => $options ); +} + +sub _process_required_option { + my ( $class, $name, $options ) = @_; + + if ( + $options->{required} + && !( + ( !exists $options->{init_arg} || defined $options->{init_arg} ) + || exists $options->{default} + || defined $options->{builder} + ) + ) { + $class->throw_error( + "You cannot have a required attribute ($name) without a default, builder, or an init_arg", + data => $options ); + } } sub initialize_instance_slot { @@ -437,12 +522,6 @@ sub _set_initial_slot_value { return $meta_instance->set_slot_value($instance, $slot_name, $value) unless $self->has_initializer; - my ($type_constraint, $can_coerce); - if ($self->has_type_constraint) { - $type_constraint = $self->type_constraint; - $can_coerce = ($self->should_coerce && $type_constraint->has_coercion); - } - my $callback = sub { my $val = $self->_coerce_and_verify( shift, $instance );; @@ -565,16 +644,42 @@ sub _check_associated_methods { sub _process_accessors { my $self = shift; my ($type, $accessor, $generate_as_inline_methods) = @_; - $accessor = (keys %$accessor)[0] if (ref($accessor)||'') eq 'HASH'; + + $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})) { + + if ( $method + && $method->isa('Class::MOP::Method::Accessor') + && $method->associated_attribute->name ne $self->name ) { + + my $other_attr_name = $method->associated_attribute->name; + my $name = $self->name; + + Carp::cluck( + "You are overwriting an accessor ($accessor) for the $other_attr_name attribute" + . " with a new accessor method for the $name attribute" ); + } + + 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" - ); + . "an accessor" ); } + + if ( !$self->associated_class->has_method($accessor) + && $self->associated_class->has_package_symbol( '&' . $accessor ) ) { + + Carp::cluck( + "You are overwriting a locally defined function ($accessor) with " + . "an accessor" ); + } + $self->SUPER::_process_accessors(@_); } @@ -585,6 +690,22 @@ sub remove_accessors { return; } +sub _inline_set_value { + my $self = shift; + my ($instance, $value) = @_; + + my $mi = $self->associated_class->get_meta_instance; + + my @code = ($self->SUPER::_inline_set_value(@_)); + + push @code, ( + $mi->inline_weaken_slot_value($instance, $self->name, $value), + 'if ref ' . $value . ';', + ) if $self->is_weak_ref; + + return @code; +} + sub install_delegation { my $self = shift; @@ -659,39 +780,26 @@ sub _canonicalize_handles { elsif (blessed($handles) && $handles->isa('Moose::Meta::TypeConstraint::DuckType')) { return map { $_ => $_ } @{ $handles->methods }; } + elsif (blessed($handles) && $handles->isa('Moose::Meta::TypeConstraint::Role')) { + $handles = $handles->role; + } else { $self->throw_error("Unable to canonicalize the 'handles' option with $handles", data => $handles); } } - else { - 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); + Class::MOP::load_class($handles); + my $role_meta = Class::MOP::class_of($handles); - return map { $_ => $_ } - grep { $_ ne 'meta' } ( - $role_meta->get_method_list, - map { $_->name } $role_meta->get_required_method_list, - ); - } -} + (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); -sub _find_delegate_metaclass { - my $self = shift; - if (my $class = $self->_isa_metadata) { - # 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) { - return Class::MOP::class_of($role); - } - else { - $self->throw_error("Cannot find delegate metaclass for attribute " . $self->name); - } + return map { $_ => $_ } + map { $_->name } + grep { !$_->isa('Class::MOP::Method::Meta') } ( + $role_meta->_get_local_methods, + $role_meta->get_required_method_list, + ); } sub _get_delegate_method_list { @@ -699,7 +807,7 @@ sub _get_delegate_method_list { my $meta = $self->_find_delegate_metaclass; if ($meta->isa('Class::MOP::Class')) { return map { $_->name } # NOTE: !never! delegate &meta - grep { $_->package_name ne 'Moose::Object' && $_->name ne 'meta' } + grep { $_->package_name ne 'Moose::Object' && !$_->isa('Class::MOP::Method::Meta') } $meta->get_all_methods; } elsif ($meta->isa('Moose::Meta::Role')) { @@ -710,6 +818,39 @@ sub _get_delegate_method_list { } } +sub _find_delegate_metaclass { + my $self = shift; + if (my $class = $self->_isa_metadata) { + unless ( Class::MOP::is_class_loaded($class) ) { + $self->throw_error( + sprintf( + 'The %s attribute is trying to delegate to a class which has not been loaded - %s', + $self->name, $class + ) + ); + } + # 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 Class::MOP::Class->initialize($class); + } + elsif (my $role = $self->_does_metadata) { + unless ( Class::MOP::is_class_loaded($class) ) { + $self->throw_error( + sprintf( + 'The %s attribute is trying to delegate to a role which has not been loaded - %s', + $self->name, $role + ) + ); + } + + return Class::MOP::class_of($role); + } + else { + $self->throw_error("Cannot find delegate metaclass for attribute " . $self->name); + } +} + sub delegation_metaclass { 'Moose::Meta::Method::Delegation' } sub _make_delegation_method { @@ -736,10 +877,8 @@ sub _coerce_and_verify { 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); - } + $val = $self->type_constraint->coerce($val) + if $self->should_coerce && $self->type_constraint->has_coercion; $self->verify_against_type_constraint($val, instance => $instance); @@ -844,7 +983,7 @@ object which does the named role. =item * coerce => $bool This option is only valid for objects with a type constraint -(C). If this is true, then coercions will be applied whenever +(C) that defined a coercion. If this is true, then coercions will be applied whenever this attribute is set. You can make both this and the C option true. @@ -900,6 +1039,29 @@ is equivalent to this: predicate => 'has_size', ); + +If your attribute name starts with an underscore (C<_>), then the clearer +and predicate will as well: + + has '_size' => ( + is => 'ro', + lazy_build => 1, + ); + +becomes: + + has '_size' => ( + is => 'ro', + lazy => 1, + builder => '_build__size', + clearer => '_clear_size', + predicate => '_has_size', + ); + +Note the doubled underscore in the builder name. Internally, Moose +simply prepends the attribute name with "_build_" to come up with the +builder name. + =item * documentation An arbitrary string that can be retrieved later by calling C<< @@ -942,7 +1104,7 @@ I Before setting the value, a check is made on the type constraint of the attribute, if it has one, to see if the value passes it. If the -value fails to pass, the set operation dies with a L. +value fails to pass, the set operation dies with a L. Any coercion to convert values is done before checking the type constraint. @@ -971,6 +1133,12 @@ methods is almost always an error.) This method overrides the parent to also remove delegation methods. +=item B<< $attr->inline_set($instance_var, $value_var) >> + +This method return a code snippet suitable for inlining the relevant +operation. It expect strings containing variable names to be used in the +inlining, like C<'$self'> or C<'$_[1]'>. + =item B<< $attr->install_delegation >> This method adds its delegation methods to the attribute's associated @@ -1028,16 +1196,16 @@ of processing on the supplied C<%options> before ultimately calling the C method. One of its main tasks is to make sure that the C<%options> provided -only includes the options returned by the -C method. +does not include the options returned by the +C method. -=item B<< $attr->legal_options_for_inheritance >> +=item B<< $attr->illegal_options_for_inheritance >> -This returns a whitelist of options that can be overridden in a +This returns a blacklist of options that can not be overridden in a subclass's attribute definition. This exists to allow a custom metaclass to change or add to the list -of options which can be changed. +of options which can not be changed. =item B<< $attr->type_constraint >>