-
package Moose::Meta::Role;
use strict;
# attributes
+my $id;
sub add_attribute {
- my $self = shift;
- my $name = shift;
- my $attr_desc;
- if (scalar @_ == 1 && ref($_[0]) eq 'HASH') {
- $attr_desc = $_[0];
- }
- else {
- $attr_desc = { @_ };
- }
- $self->get_attribute_map->{$name} = $attr_desc;
+ my $self = shift;
+ # either we have an attribute object already
+ # or we need to create one from the args provided
+ require Moose::Meta::Attribute;
+ my $attribute = blessed($_[0]) ? $_[0] : $self->_role_meta->attribute_metaclass->new(@_);
+ # make sure it is derived from the correct type though
+ ($attribute->isa('Class::MOP::Attribute'))
+ || confess "Your attribute must be an instance of Class::MOP::Attribute (or a subclass)";
+ $attribute->attach_to_class($self->_role_meta);
+ # FIXME attribute vs. method shadowing # $attribute->install_accessors();
+ $self->get_attribute_map->{$attribute->name} = $attribute;
+
+ $attribute->{__id} ||= ++$id;
+
+ $self->remove_required_methods(
+ grep { defined }
+ map { $attribute->$_ }
+ qw/accessor reader writer/
+ );
+
+ # FIXME
+ # in theory we have to tell everyone the slot structure may have changed
}
sub has_attribute {
- my ($self, $name) = @_;
- exists $self->get_attribute_map->{$name} ? 1 : 0;
-}
+ my ($self, $attribute_name) = @_;
+ (defined $attribute_name && $attribute_name)
+ || confess "You must define an attribute name";
+ exists $self->get_attribute_map->{$attribute_name} ? 1 : 0;
+}
sub get_attribute {
- my ($self, $name) = @_;
- $self->get_attribute_map->{$name}
+ my ($self, $attribute_name) = @_;
+ (defined $attribute_name && $attribute_name)
+ || confess "You must define an attribute name";
+ return $self->get_attribute_map->{$attribute_name}
+ if $self->has_attribute($attribute_name);
+ return;
}
sub remove_attribute {
- my ($self, $name) = @_;
- delete $self->get_attribute_map->{$name}
-}
+ my ($self, $attribute_name) = @_;
+ (defined $attribute_name && $attribute_name)
+ || confess "You must define an attribute name";
+ my $removed_attribute = $self->get_attribute_map->{$attribute_name};
+ return unless defined $removed_attribute;
+ delete $self->get_attribute_map->{$attribute_name};
+ $removed_attribute->remove_accessors();
+ $removed_attribute->detach_from_class();
+ return $removed_attribute;
+}
sub get_attribute_list {
my ($self) = @_;
keys %{$self->get_attribute_map};
}
+sub compute_all_applicable_attributes {
+ my $self = shift;
+ my @attrs;
+
+ my %attrs;
+
+ foreach my $role (@{ $self->get_roles() }) {
+ foreach my $attr ( $role->compute_all_applicable_attributes ) {
+ push @{ $attrs{$attr->name} ||= [] }, $attr;
+ }
+ }
+
+ # remove all conflicting attributes
+ foreach my $attr_name ( keys %attrs ) {
+ if ( @{ $attrs{$attr_name} } == 1 ) {
+ $attrs{$attr_name} = $attrs{$attr_name}[0];
+ } else {
+ delete $attrs{$attr_name};
+ }
+ }
+
+ # overlay our own attributes
+ my @local_attr_list = $self->get_attribute_list;
+ @attrs{@local_attr_list} = map { $self->get_attribute($_) } @local_attr_list;
+
+ return values %attrs;
+}
+
+sub find_attribute_by_name {
+ my ($self, $attr_name) = @_;
+ # keep a record of what we have seen
+ # here, this will handle all the
+ # inheritence issues because we are
+ # using the &class_precedence_list
+
+ if ( $self->has_attribute( $attr_name ) ) {
+ return $self->get_attribute( $attr_name );
+ } else {
+ my @found;
+ foreach my $role ( @{ $self->get_roles } ) {
+ if ( my $attr = $role->find_attribute_by_name ) {
+ push @found, $attr;
+ }
+ }
+
+ if ( @found == 1 ) {
+ # there's no conflict
+ return $found[0];
+ } else {
+ return;
+ }
+ }
+}
## applying a role to a class ...
# attribute accessors. However I am thinking
# that maybe those are somehow exempt from
# the require methods stuff.
+
+ ### FIXME
+ # attributes' accessors are not being treated as first class methods as far as role composition is concerned.
+
foreach my $required_method_name ($self->get_required_method_list) {
unless ($other->find_method_by_name($required_method_name)) {
sub _apply_attributes {
my ($self, $other) = @_;
- foreach my $attribute_name ($self->get_attribute_list) {
+ foreach my $attr ($self->compute_all_applicable_attributes) {
# it if it has one already
- if ($other->has_attribute($attribute_name) &&
- # make sure we haven't seen this one already too
- $other->get_attribute($attribute_name) != $self->get_attribute($attribute_name)) {
+ my $other_attr = $other->find_attribute_by_name($attr->name);
+
+ # __id is a hack to allow cloned attrs to compare as equal
+ if ( $other_attr && !(( exists ($other_attr->{__id}) && exists($other_attr->{__id}) && $other_attr->{__id} == $attr->{__id} ) || $other_attr == $attr ) ) {
# see if we are being composed
# into a role or not
if ($other->isa('Moose::Meta::Role')) {
# all attribute conflicts between roles
# result in an immediate fatal error
+ # FIXME - do they
confess "Role '" . $self->name . "' has encountered an attribute conflict " .
"during composition. This is fatal error and cannot be disambiguated.";
}
}
}
else {
- $other->add_attribute(
- $attribute_name,
- $self->get_attribute($attribute_name)
- );
+ my $clone = $attr->meta->clone_object( $attr );
+ $other->add_attribute( $clone );
}
}
}
my ($self, $other) = @_;
foreach my $method_name ($self->get_method_list) {
# it if it has one already
- if ($other->has_method($method_name) &&
- # and if they are not the same thing ...
- $other->get_method($method_name) != $self->get_method($method_name)) {
+ my $other_method = $other->find_method_by_name($method_name);
+ if ($other_method && $other_method != $self->get_method($method_name)) {
# see if we are composing into a role
if ($other->isa('Moose::Meta::Role')) {
# method conflicts between roles result
--- /dev/null
+#!/usr/bin/perl
+
+# FIXME
+## Add variants for everything
+## with delegation
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+use Test::Exception;
+
+# a role requires methods
+# this requirement can be satisfied by a matrix multiplication:
+# either via an attribute (an accessor or a delegation)
+# or with an "actual" method
+# which is provided via either the class, a base class, or a role
+
+###########################
+# Keep the noise level down
+no warnings 'redefine';
+sub Test::Builder::diag { }
+###########################
+
+
+{
+ package Role::Requires;
+ use Moose::Role;
+
+ requires "foo";
+
+ package Role::Provides;
+ use Moose::Role;
+
+ sub foo { __PACKAGE__ . "::foo" }
+
+ package Class::Provides;
+ use Moose;
+
+ sub foo { __PACKAGE__ . "::foo" }
+}
+
+{
+ package Class::Provider::class::Type::method::WithRole::requires;
+ use Moose;
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ ::lives_ok { with 'Role::Requires' } "composed role that requires foo";
+
+ ::ok( __PACKAGE__->does('Role::Requires'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::method::foo", "foo method is from this class" );
+}
+
+{
+ package Class::Provider::class::Type::method::WithRole::provides;
+ use Moose;
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ ::lives_ok { with 'Role::Provides' } "composed role that provides foo";
+
+ ::ok( __PACKAGE__->does('Role::Provides'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::method::foo", "foo method is from this class" );
+}
+
+{
+ package Class::Provider::class::Type::attr::WithRole::requires;
+ use Moose;
+
+ ::dies_ok { with 'Role::Requires' } "can't compose the role that requires foo yet";
+
+ has foo => (
+ isa => "Str",
+ is => "rw",
+ default => __PACKAGE__ . "::attr::foo",
+ );
+
+ ::lives_ok { with 'Role::Requires' } "composed role that requires foo";
+
+ ::ok( __PACKAGE__->does('Role::Requires'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::attr::foo", "foo method is from an attr in this class" );
+}
+
+{
+ package Class::Provider::class::Type::attr::WithRole::provides;
+ use Moose;
+
+ has foo => (
+ isa => "Str",
+ is => "rw",
+ default => __PACKAGE__ . "::attr::foo",
+ );
+
+ ::lives_ok { with 'Role::Provides' } "composed role that provides foo";
+
+ ::ok( __PACKAGE__->does('Role::Provides'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::attr::foo", "foo method is from an attr in this class" );
+}
+
+{
+ package Class::Provider::baseclass::Type::method::WithRole::requires;
+ use Moose;
+
+ ::dies_ok { with 'Role::Requires' } "can't compose the role that requires foo yet";
+
+ extends "Class::Provides";
+
+ ::lives_ok { with 'Role::Requires' } "composed role that requires foo, foo satisfied by base class";
+ ::ok( __PACKAGE__->does('Role::Requires'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, "Class::Provides::foo", "method came from base class" );
+}
+
+{
+ package Class::Provider::baseclass::Type::method::WithRole::provides;
+ use Moose;
+
+ extends "Class::Provides";
+
+ ::lives_ok { with 'Role::Provides' } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provides'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, "Class::Provides::foo", "role didn't overwrite method from base class" );
+}
+
+
+{
+ package Role::Provider::role::Type::method::WithRole::requires;
+ use Moose::Role;
+
+ with "Role::Requires";
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+}
+
+{
+ package Role::Provider::role::Type::attr::WithRole::requires;
+ use Moose::Role;
+
+ with "Role::Requires";
+
+ has foo => (
+ isa => "Str",
+ is => "rw",
+ default => __PACKAGE__ . "::attr::foo",
+ );
+}
+
+{
+ package Role::Provider::role::Type::attr::WithRole::provides;
+ use Moose::Role;
+
+ with "Role::Provides";
+
+ has foo => (
+ isa => "Str",
+ is => "rw",
+ default => __PACKAGE__ . "::attr::foo",
+ );
+}
+
+
+{
+ package Role::Provider::baserole::Type::method;
+ use Moose::Role;
+
+ with "Role::Provides";
+}
+
+
+{
+ package Role::Provider::baserole::Type::attr::WithRole::requires::base;
+ use Moose::Role;
+
+ with "Role::Requires";
+
+ has foo => (
+ isa => "Str",
+ is => "rw",
+ default => __PACKAGE__ . "::attr::foo",
+ );
+
+ package Role::Provider::baserole::Type::attr::WithRole::requires;
+ use Moose::Role;
+
+ with "Role::Provider::baserole::Type::attr::WithRole::requires::base";
+
+ package Role::Provider::baserole::Type::attr::WithRole::requires::overridden;
+ use Moose::Role;
+
+ with "Role::Provider::baserole::Type::attr::WithRole::requires::base";
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+}
+
+{
+ package Role::Provider::baserole::Type::method::WithRole::requires::base;
+ use Moose::Role;
+
+ with "Role::Requires";
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ package Role::Provider::baserole::Type::method::WithRole::requires::overridden;
+ use Moose::Role;
+
+ with "Role::Provider::baserole::Type::method::WithRole::requires::base";
+
+ has foo => (
+ isa => "Str",
+ is => "rw",
+ default => __PACKAGE__ . "::attr::foo",
+ );
+}
+
+##############################
+
+
+{
+ package Role::Provider::role::Type::method::WithRole::requires::ClassWithNothing;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::role::Type::method::WithRole::requires" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::role::Type::method::WithRole::requires'), "package does role" );
+ ::ok( __PACKAGE__->does('Role::Requires'), "package does base role" );
+
+ ::is( __PACKAGE__->new->foo, "Role::Provider::role::Type::method::WithRole::requires::method::foo", "foo was defined in the role" );
+}
+
+{
+ package Role::Provider::role::Type::method::WithRole::requires::ClassWithMethod;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::role::Type::method::WithRole::requires" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Requires'), "package does role" );
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::method::foo", "foo was defined in the class" );
+}
+
+{
+ package Role::Provider::role::Type::method::WithRole::requires::ClassWithBase;
+ use Moose;
+
+ extends "Class::Provides";
+
+ ::lives_ok { with "Role::Provider::role::Type::method::WithRole::requires" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Requires'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, "Class::Provides::foo", "foo was defined in the base class" );
+}
+
+##
+
+{
+ package Role::Provider::role::Type::attr::WithRole::requires::ClassWithNothing;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::role::Type::attr::WithRole::requires" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Requires'), "package does role" );
+
+ ::is( eval { __PACKAGE__->new->foo }, "Role::Provider::role::Type::attr::WithRole::requires::attr::foo", "foo was defined in the class" );
+}
+
+{
+ package Role::Provider::role::Type::attr::WithRole::requires::ClassWithMethod;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::role::Type::attr::WithRole::requires" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Requires'), "package does role" );
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::method::foo", "foo was defined in the class" );
+}
+
+{
+ package Role::Provider::role::Type::attr::WithRole::requires::ClassWithBase;
+ use Moose;
+
+ extends "Class::Provides";
+
+ ::lives_ok { with "Role::Provider::role::Type::attr::WithRole::requires" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Requires'), "package does role" );
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, "Class::Provides::foo", "foo was defined in the base class" );
+}
+
+##
+
+{
+ package Role::Provider::role::Type::attr::WithRole::provides::ClassWithNothing;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::role::Type::attr::WithRole::provides" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provides'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, "Role::Provider::role::Type::attr::WithRole::provides::attr::foo", "foo was defined in the role" );
+}
+
+{
+ package Role::Provider::role::Type::attr::WithRole::provides::ClassWithMethod;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::role::Type::attr::WithRole::provides" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provides'), "package does role" );
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::method::foo", "foo was defined in the class" );
+}
+
+{
+ package Role::Provider::role::Type::attr::WithRole::provides::ClassWithBase;
+ use Moose;
+
+ extends "Class::Provides";
+
+ ::lives_ok { with "Role::Provider::role::Type::attr::WithRole::provides" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provides'), "package does role" );
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, "Class::Provides::foo", "foo was defined in the base class" );
+}
+
+##
+
+{
+ package Role::Provider::baserole::Type::method::ClassWithNothing;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::baserole::Type::method" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provides'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, "Role::Provides::foo", "foo was defined in the base role" );
+}
+
+{
+ package Role::Provider::baserole::Type::method::ClassWithMethod;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::baserole::Type::method" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provides'), "package does role" );
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::method::foo", "foo was defined in the role" );
+}
+
+{
+ package Role::Provider::baserole::Type::method::ClassWithBase;
+ use Moose;
+
+ extends "Class::Provides";
+
+ ::lives_ok { with "Role::Provider::baserole::Type::method" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provides'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, "Class::Provides::foo", "foo was defined in the base class" );
+}
+
+##
+
+{
+ package Role::Provider::baserole::Type::attr::WithRole::requires::ClassWithNothing;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::baserole::Type::attr::WithRole::requires" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::baserole::Type::attr::WithRole::requires'), "package does role" );
+
+ ::is( eval { __PACKAGE__->new->foo }, "Role::Provider::baserole::Type::attr::WithRole::requires::base::attr::foo", "foo was defined in the base role" );
+}
+
+{
+ package Role::Provider::baserole::Type::attr::WithRole::requires::ClassWithMethod;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::baserole::Type::attr::WithRole::requires" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::baserole::Type::attr::WithRole::requires'), "package does role" );
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::method::foo", "foo was defined in the class" );
+}
+
+{
+ package Role::Provider::baserole::Type::attr::WithRole::requires::ClassWithBase;
+ use Moose;
+
+ extends "Class::Provides";
+
+ ::lives_ok { with "Role::Provider::baserole::Type::attr::WithRole::requires" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::baserole::Type::attr::WithRole::requires'), "package does role" );
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, "Class::Provides::foo", "foo was defined in the base class" );
+}
+
+
+##
+
+{
+ package Role::Provider::baserole::Type::attr::WithRole::requires::overridden::ClassWithNothing;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::baserole::Type::attr::WithRole::requires::overridden" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::baserole::Type::attr::WithRole::requires::overridden'), "package does role" );
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( eval { __PACKAGE__->new->foo}, "Role::Provider::baserole::Type::attr::WithRole::requires::overridden::method::foo", "foo was defined in the overridding role" );
+}
+
+{
+ package Role::Provider::baserole::Type::attr::WithRole::requires::overridden::ClassWithMethod;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::baserole::Type::attr::WithRole::requires::overridden" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::baserole::Type::attr::WithRole::requires::overridden'), "package does role" );
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::method::foo", "foo was defined in the class" );
+}
+
+{
+ package Role::Provider::baserole::Type::attr::WithRole::requires::overridden::ClassWithBase;
+ use Moose;
+
+ extends "Class::Provides";
+
+ ::lives_ok { with "Role::Provider::baserole::Type::attr::WithRole::requires::overridden" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::baserole::Type::attr::WithRole::requires::overridden'), "package does role" );
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, "Class::Provides::foo", "foo was defined in the base class" );
+}
+
+##
+
+{
+ package Role::Provider::baserole::Type::method::WithRole::requires::overridden::ClassWithNothing;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::baserole::Type::method::WithRole::requires::overridden" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::baserole::Type::method::WithRole::requires::overridden'), "package does role" );
+
+ ::is( __PACKAGE__->new->foo, "Role::Provider::baserole::Type::method::WithRole::requires::overridden::attr::foo", "foo was defined in the overriding role" );
+}
+
+{
+ package Role::Provider::baserole::Type::method::WithRole::requires::overridden::ClassWithMethod;
+ use Moose;
+
+ ::lives_ok { with "Role::Provider::baserole::Type::method::WithRole::requires::overridden" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::baserole::Type::method::WithRole::requires::overridden'), "package does role" );
+
+ sub foo { __PACKAGE__ . "::method::foo" }
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, __PACKAGE__ . "::method::foo", "foo was defined in the class" );
+}
+
+{
+ package Role::Provider::baserole::Type::method::WithRole::requires::overridden::ClassWithBase;
+ use Moose;
+
+ extends "Class::Provides";
+
+ ::lives_ok { with "Role::Provider::baserole::Type::method::WithRole::requires::overridden" } "composed role that provides foo";
+ ::ok( __PACKAGE__->does('Role::Provider::baserole::Type::method::WithRole::requires::overridden'), "package does role" );
+
+ local our $TODO = "Attribute vs. method shadowing not yet clear";
+ ::is( __PACKAGE__->new->foo, "Class::Provides::foo", "foo was defined in the base class" );
+}