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',
# ...
-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
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
# 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;
|| $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}) {
|| $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:
# 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 {
}
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 {
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 );;
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(@_);
}
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;
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 {
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')) {
}
}
+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 {
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);
=item * coerce => $bool
This option is only valid for objects with a type constraint
-(C<isa>). If this is true, then coercions will be applied whenever
+(C<isa>) 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<weak_ref> option true.
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<<
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<throw_error>.
+value fails to pass, the set operation dies with a L</throw_error>.
Any coercion to convert values is done before checking the type constraint.
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
the C<clone> method.
One of its main tasks is to make sure that the C<%options> provided
-only includes the options returned by the
-C<legal_options_for_inheritance> method.
+does not include the options returned by the
+C<illegal_options_for_inheritance> 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 >>