move core meta-attribute attributes to a mixin class for benefit of role attributes
Dave Rolsky [Sat, 26 Dec 2009 01:52:52 +0000 (19:52 -0600)]
lib/Class/MOP.pm
lib/Class/MOP/Attribute.pm
lib/Class/MOP/Mixin/AttributeBase.pm [new file with mode: 0644]
t/000_load.t
t/014_attribute_introspection.t
xs/Attribute.xs [deleted file]
xs/AttributeBase.xs [new file with mode: 0644]
xs/MOP.xs

index 5749ce7..0ae3a0c 100644 (file)
@@ -12,6 +12,7 @@ use Carp          'confess';
 use Scalar::Util  'weaken', 'reftype', 'blessed';
 use Try::Tiny;
 
+use Class::MOP::Mixin::AttributeBase;
 use Class::MOP::Mixin::HasAttributes;
 use Class::MOP::Mixin::HasMethods;
 use Class::MOP::Class;
@@ -379,9 +380,8 @@ Class::MOP::Class->meta->add_attribute(
 # _construct_class_instance method.
 
 ## --------------------------------------------------------
-## Class::MOP::Attribute
-
-Class::MOP::Attribute->meta->add_attribute(
+## Class::MOP::Mixin::AttributeBase
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('name' => (
         reader   => {
             # NOTE: we need to do this in order
@@ -390,106 +390,108 @@ Class::MOP::Attribute->meta->add_attribute(
             #
             # we just alias the original method
             # rather than re-produce it here
-            'name' => \&Class::MOP::Attribute::name
+            'name' => \&Class::MOP::Mixin::AttributeBase::name
         }
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
-    Class::MOP::Attribute->new('associated_class' => (
-        reader   => {
-            # NOTE: we need to do this in order
-            # for the instance meta-object to
-            # not fall into meta-circular death
-            #
-            # we just alias the original method
-            # rather than re-produce it here
-            'associated_class' => \&Class::MOP::Attribute::associated_class
-        }
-    ))
-);
-
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('accessor' => (
-        reader    => { 'accessor'     => \&Class::MOP::Attribute::accessor     },
-        predicate => { 'has_accessor' => \&Class::MOP::Attribute::has_accessor },
+        reader    => { 'accessor'     => \&Class::MOP::Mixin::AttributeBase::accessor     },
+        predicate => { 'has_accessor' => \&Class::MOP::Mixin::AttributeBase::has_accessor },
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('reader' => (
-        reader    => { 'reader'     => \&Class::MOP::Attribute::reader     },
-        predicate => { 'has_reader' => \&Class::MOP::Attribute::has_reader },
+        reader    => { 'reader'     => \&Class::MOP::Mixin::AttributeBase::reader     },
+        predicate => { 'has_reader' => \&Class::MOP::Mixin::AttributeBase::has_reader },
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('initializer' => (
-        reader    => { 'initializer'     => \&Class::MOP::Attribute::initializer     },
-        predicate => { 'has_initializer' => \&Class::MOP::Attribute::has_initializer },
+        reader    => { 'initializer'     => \&Class::MOP::Mixin::AttributeBase::initializer     },
+        predicate => { 'has_initializer' => \&Class::MOP::Mixin::AttributeBase::has_initializer },
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('definition_context' => (
-        reader    => { 'definition_context'     => \&Class::MOP::Attribute::definition_context     },
+        reader    => { 'definition_context'     => \&Class::MOP::Mixin::AttributeBase::definition_context     },
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('writer' => (
-        reader    => { 'writer'     => \&Class::MOP::Attribute::writer     },
-        predicate => { 'has_writer' => \&Class::MOP::Attribute::has_writer },
+        reader    => { 'writer'     => \&Class::MOP::Mixin::AttributeBase::writer     },
+        predicate => { 'has_writer' => \&Class::MOP::Mixin::AttributeBase::has_writer },
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('predicate' => (
-        reader    => { 'predicate'     => \&Class::MOP::Attribute::predicate     },
-        predicate => { 'has_predicate' => \&Class::MOP::Attribute::has_predicate },
+        reader    => { 'predicate'     => \&Class::MOP::Mixin::AttributeBase::predicate     },
+        predicate => { 'has_predicate' => \&Class::MOP::Mixin::AttributeBase::has_predicate },
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('clearer' => (
-        reader    => { 'clearer'     => \&Class::MOP::Attribute::clearer     },
-        predicate => { 'has_clearer' => \&Class::MOP::Attribute::has_clearer },
+        reader    => { 'clearer'     => \&Class::MOP::Mixin::AttributeBase::clearer     },
+        predicate => { 'has_clearer' => \&Class::MOP::Mixin::AttributeBase::has_clearer },
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('builder' => (
-        reader    => { 'builder'     => \&Class::MOP::Attribute::builder     },
-        predicate => { 'has_builder' => \&Class::MOP::Attribute::has_builder },
+        reader    => { 'builder'     => \&Class::MOP::Mixin::AttributeBase::builder     },
+        predicate => { 'has_builder' => \&Class::MOP::Mixin::AttributeBase::has_builder },
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('init_arg' => (
-        reader    => { 'init_arg'     => \&Class::MOP::Attribute::init_arg     },
-        predicate => { 'has_init_arg' => \&Class::MOP::Attribute::has_init_arg },
+        reader    => { 'init_arg'     => \&Class::MOP::Mixin::AttributeBase::init_arg     },
+        predicate => { 'has_init_arg' => \&Class::MOP::Mixin::AttributeBase::has_init_arg },
     ))
 );
 
-Class::MOP::Attribute->meta->add_attribute(
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
     Class::MOP::Attribute->new('default' => (
         # default has a custom 'reader' method ...
-        predicate => { 'has_default' => \&Class::MOP::Attribute::has_default },
+        predicate => { 'has_default' => \&Class::MOP::Mixin::AttributeBase::has_default },
     ))
 );
 
+Class::MOP::Mixin::AttributeBase->meta->add_attribute(
+    Class::MOP::Attribute->new('insertion_order' => (
+        reader      => { 'insertion_order' => \&Class::MOP::Mixin::AttributeBase::insertion_order },
+        writer      => { '_set_insertion_order' => \&Class::MOP::Mixin::AttributeBase::_set_insertion_order },
+        predicate   => { 'has_insertion_order' => \&Class::MOP::Mixin::AttributeBase::has_insertion_order },
+    ))
+);
+
+## --------------------------------------------------------
+## Class::MOP::Attribute
 Class::MOP::Attribute->meta->add_attribute(
-    Class::MOP::Attribute->new('associated_methods' => (
-        reader   => { 'associated_methods' => \&Class::MOP::Attribute::associated_methods },
-        default  => sub { [] }
+    Class::MOP::Attribute->new('associated_class' => (
+        reader   => {
+            # NOTE: we need to do this in order
+            # for the instance meta-object to
+            # not fall into meta-circular death
+            #
+            # we just alias the original method
+            # rather than re-produce it here
+            'associated_class' => \&Class::MOP::Attribute::associated_class
+        }
     ))
 );
 
 Class::MOP::Attribute->meta->add_attribute(
-    Class::MOP::Attribute->new('insertion_order' => (
-        reader      => { 'insertion_order' => \&Class::MOP::Attribute::insertion_order },
-        writer      => { '_set_insertion_order' => \&Class::MOP::Attribute::_set_insertion_order },
-        predicate   => { 'has_insertion_order' => \&Class::MOP::Attribute::has_insertion_order },
+    Class::MOP::Attribute->new('associated_methods' => (
+        reader   => { 'associated_methods' => \&Class::MOP::Attribute::associated_methods },
+        default  => sub { [] }
     ))
 );
 
@@ -697,6 +699,7 @@ $_->meta->make_immutable(
     constructor_name    => undef,
     inline_accessors => 0,
 ) for qw/
+    Class::MOP::Mixin::AttributeBase
     Class::MOP::Mixin::HasAttributes
     Class::MOP::Mixin::HasMethods
 /;
index b9ca6d2..7bd6839 100644 (file)
@@ -14,7 +14,7 @@ our $VERSION   = '0.97';
 $VERSION = eval $VERSION;
 our $AUTHORITY = 'cpan:STEVAN';
 
-use base 'Class::MOP::Object';
+use base 'Class::MOP::Object', 'Class::MOP::Mixin::AttributeBase';
 
 # NOTE: (meta-circularity)
 # This method will be replaced in the
@@ -44,7 +44,7 @@ sub new {
         confess("Setting both default and builder is not allowed.")
             if exists $options{default};
     } else {
-        (is_default_a_coderef(\%options))
+        ($class->is_default_a_coderef(\%options))
             || confess("References are not allowed as default values, you must ".
                        "wrap the default of '$name' in a CODE reference (ex: sub { [] } and not [])")
                 if exists $options{default} && ref $options{default};
@@ -156,42 +156,9 @@ sub _set_initial_slot_value {
     $instance->$initializer($value, $callback, $self);
 }
 
-# NOTE:
-# the next bunch of methods will get bootstrapped
-# away in the Class::MOP bootstrapping section
-
 sub associated_class   { $_[0]->{'associated_class'}   }
 sub associated_methods { $_[0]->{'associated_methods'} }
 
-sub has_accessor    { defined($_[0]->{'accessor'}) }
-sub has_reader      { defined($_[0]->{'reader'}) }
-sub has_writer      { defined($_[0]->{'writer'}) }
-sub has_predicate   { defined($_[0]->{'predicate'}) }
-sub has_clearer     { defined($_[0]->{'clearer'}) }
-sub has_builder     { defined($_[0]->{'builder'}) }
-sub has_init_arg    { defined($_[0]->{'init_arg'}) }
-sub has_default     { defined($_[0]->{'default'}) }
-sub has_initializer { defined($_[0]->{'initializer'}) }
-sub has_insertion_order { defined($_[0]->{'insertion_order'}) }
-
-sub accessor           { $_[0]->{'accessor'}    }
-sub reader             { $_[0]->{'reader'}      }
-sub writer             { $_[0]->{'writer'}      }
-sub predicate          { $_[0]->{'predicate'}   }
-sub clearer            { $_[0]->{'clearer'}     }
-sub builder            { $_[0]->{'builder'}     }
-sub init_arg           { $_[0]->{'init_arg'}    }
-sub initializer        { $_[0]->{'initializer'} }
-sub definition_context { $_[0]->{'definition_context'} }
-sub insertion_order    { $_[0]->{'insertion_order'} }
-sub _set_insertion_order { $_[0]->{'insertion_order'} = $_[1] }
-
-# end bootstrapped away method section.
-# (all methods below here are kept intact)
-
-sub has_read_method  { $_[0]->has_reader || $_[0]->has_accessor }
-sub has_write_method { $_[0]->has_writer || $_[0]->has_accessor }
-
 sub get_read_method  { 
     my $self   = shift;    
     my $reader = $self->reader || $self->accessor;
@@ -252,24 +219,6 @@ sub get_write_method_ref {
     }
 }
 
-sub is_default_a_coderef {
-    my ($value) = $_[0]->{'default'};
-    return unless ref($value);
-    return ref($value) eq 'CODE' || (blessed($value) && $value->isa('Class::MOP::Method'));
-}
-
-sub default {
-    my ($self, $instance) = @_;
-    if (defined $instance && $self->is_default_a_coderef) {
-        # if the default is a CODE ref, then
-        # we pass in the instance and default
-        # can return a value based on that
-        # instance. Somewhat crude, but works.
-        return $self->{'default'}->($instance);
-    }
-    $self->{'default'};
-}
-
 # slots
 
 sub slots { (shift)->name }
diff --git a/lib/Class/MOP/Mixin/AttributeBase.pm b/lib/Class/MOP/Mixin/AttributeBase.pm
new file mode 100644 (file)
index 0000000..62d84a4
--- /dev/null
@@ -0,0 +1,62 @@
+package Class::MOP::Mixin::AttributeBase;
+
+use strict;
+use warnings;
+
+use Scalar::Util 'blessed';
+
+our $VERSION = '0.97';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use base 'Class::MOP::Object';
+
+sub has_accessor        { defined $_[0]->{'accessor'} }
+sub has_reader          { defined $_[0]->{'reader'} }
+sub has_writer          { defined $_[0]->{'writer'} }
+sub has_predicate       { defined $_[0]->{'predicate'} }
+sub has_clearer         { defined $_[0]->{'clearer'} }
+sub has_builder         { defined $_[0]->{'builder'} }
+sub has_init_arg        { defined $_[0]->{'init_arg'} }
+sub has_default         { defined $_[0]->{'default'} }
+sub has_initializer     { defined $_[0]->{'initializer'} }
+sub has_insertion_order { defined $_[0]->{'insertion_order'} }
+
+sub accessor             { $_[0]->{'accessor'} }
+sub reader               { $_[0]->{'reader'} }
+sub writer               { $_[0]->{'writer'} }
+sub predicate            { $_[0]->{'predicate'} }
+sub clearer              { $_[0]->{'clearer'} }
+sub builder              { $_[0]->{'builder'} }
+sub init_arg             { $_[0]->{'init_arg'} }
+sub initializer          { $_[0]->{'initializer'} }
+sub definition_context   { $_[0]->{'definition_context'} }
+sub insertion_order      { $_[0]->{'insertion_order'} }
+sub _set_insertion_order { $_[0]->{'insertion_order'} = $_[1] }
+
+sub has_read_method  { $_[0]->has_reader || $_[0]->has_accessor }
+sub has_write_method { $_[0]->has_writer || $_[0]->has_accessor }
+
+sub is_default_a_coderef {
+    # Uber hack because it is called from CMOP::Attribute constructor as
+    # $class->is_default_a_coderef(\%options)
+    my ($value) = ref $_[0] ? $_[0]->{'default'} : $_[1]->{'default'};
+
+    return unless ref($value);
+
+    return ref($value) eq 'CODE'
+        || ( blessed($value) && $value->isa('Class::MOP::Method') );
+}
+
+sub default {
+    my ( $self, $instance ) = @_;
+    if ( defined $instance && $self->is_default_a_coderef ) {
+        # if the default is a CODE ref, then we pass in the instance and
+        # default can return a value based on that instance. Somewhat crude,
+        # but works.
+        return $self->{'default'}->($instance);
+    }
+    $self->{'default'};
+}
+
+1;
index 47ce599..6060948 100644 (file)
@@ -5,6 +5,7 @@ use Test::More;
 
 BEGIN {
     use_ok('Class::MOP');
+    use_ok('Class::MOP::Mixin::AttributeBase');
     use_ok('Class::MOP::Mixin::HasAttributes');
     use_ok('Class::MOP::Mixin::HasMethods');
     use_ok('Class::MOP::Package');
@@ -31,6 +32,7 @@ my %METAS = (
     'Class::MOP::Method::Accessor'  => Class::MOP::Method::Accessor->meta,
     'Class::MOP::Method::Constructor' =>
         Class::MOP::Method::Constructor->meta,
+    'Class::MOP::Mixin::AttributeBase'   => Class::MOP::Mixin::AttributeBase->meta,
     'Class::MOP::Mixin::HasAttributes'   => Class::MOP::Mixin::HasAttributes->meta,
     'Class::MOP::Mixin::HasMethods'      => Class::MOP::Mixin::HasMethods->meta,
     'Class::MOP::Package'         => Class::MOP::Package->meta,
@@ -81,6 +83,7 @@ is_deeply(
         Class::MOP::Method::Generated->meta,
         Class::MOP::Method::Inlined->meta,
         Class::MOP::Method::Wrapped->meta,
+        Class::MOP::Mixin::AttributeBase->meta,
         Class::MOP::Mixin::HasAttributes->meta,
         Class::MOP::Mixin::HasMethods->meta,
         Class::MOP::Module->meta,
@@ -98,6 +101,7 @@ is_deeply(
             Class::MOP::Class
             Class::MOP::Class::Immutable::Class::MOP::Class
             Class::MOP::Class::Immutable::Trait
+            Class::MOP::Mixin::AttributeBase
             Class::MOP::Mixin::HasAttributes
             Class::MOP::Mixin::HasMethods
             Class::MOP::Instance
index d221389..e38b616 100644 (file)
@@ -8,12 +8,13 @@ use Class::MOP;
 
 {
     my $attr = Class::MOP::Attribute->new('$test');
-    is($attr->meta, Class::MOP::Attribute->meta, '... instance and class both lead to the same meta');
+    is( $attr->meta, Class::MOP::Attribute->meta,
+        '... instance and class both lead to the same meta' );
 }
 
 {
     my $meta = Class::MOP::Attribute->meta();
-    isa_ok($meta, 'Class::MOP::Class');
+    isa_ok( $meta, 'Class::MOP::Class' );
 
     my @methods = qw(
         new
@@ -62,15 +63,20 @@ use Class::MOP;
         remove_accessors
 
         _new
-        );
+    );
 
     is_deeply(
-        [ sort $meta->get_method_list ],
+        [
+            sort Class::MOP::Mixin::AttributeBase->meta->get_method_list,
+            $meta->get_method_list
+        ],
         [ sort @methods ],
-        '... our method list matches');
+        '... our method list matches'
+    );
 
     foreach my $method_name (@methods) {
-        ok($meta->has_method($method_name), '... Class::MOP::Attribute->has_method(' . $method_name . ')');
+        ok( $meta->find_method_by_name($method_name),
+            '... Class::MOP::Attribute->find_method_by_name(' . $method_name . ')' );
     }
 
     my @attributes = (
@@ -91,12 +97,19 @@ use Class::MOP;
     );
 
     is_deeply(
-        [ sort $meta->get_attribute_list ],
+        [
+            sort Class::MOP::Mixin::AttributeBase->meta->get_attribute_list,
+            $meta->get_attribute_list
+        ],
         [ sort @attributes ],
-        '... our attribute list matches');
+        '... our attribute list matches'
+    );
 
     foreach my $attribute_name (@attributes) {
-        ok($meta->has_attribute($attribute_name), '... Class::MOP::Attribute->has_attribute(' . $attribute_name . ')');
+        ok( $meta->find_attribute_by_name($attribute_name),
+                  '... Class::MOP::Attribute->find_attribute_by_name('
+                . $attribute_name
+                . ')' );
     }
 
     # We could add some tests here to make sure that
diff --git a/xs/Attribute.xs b/xs/Attribute.xs
deleted file mode 100644 (file)
index 0375cb4..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#include "mop.h"
-
-MODULE = Class::MOP::Attribute   PACKAGE = Class::MOP::Attribute
-
-PROTOTYPES: DISABLE
-
-BOOT:
-    INSTALL_SIMPLE_READER(Attribute, name);
diff --git a/xs/AttributeBase.xs b/xs/AttributeBase.xs
new file mode 100644 (file)
index 0000000..b29a14c
--- /dev/null
@@ -0,0 +1,8 @@
+#include "mop.h"
+
+MODULE = Class::MOP::Mixin::AttributeBase   PACKAGE = Class::MOP::Mixin::AttributeBase
+
+PROTOTYPES: DISABLE
+
+BOOT:
+    INSTALL_SIMPLE_READER(Mixin::AttributeBase, name);
index a0a29fd..faa7704 100644 (file)
--- a/xs/MOP.xs
+++ b/xs/MOP.xs
@@ -13,7 +13,7 @@ find_method (const char *key, STRLEN keylen, SV *val, void *ud)
 
 EXTERN_C XS(boot_Class__MOP__Mixin__HasMethods);
 EXTERN_C XS(boot_Class__MOP__Package);
-EXTERN_C XS(boot_Class__MOP__Attribute);
+EXTERN_C XS(boot_Class__MOP__Mixin__AttributeBase);
 EXTERN_C XS(boot_Class__MOP__Method);
 
 MODULE = Class::MOP   PACKAGE = Class::MOP
@@ -25,7 +25,7 @@ BOOT:
 
     MOP_CALL_BOOT (boot_Class__MOP__Mixin__HasMethods);
     MOP_CALL_BOOT (boot_Class__MOP__Package);
-    MOP_CALL_BOOT (boot_Class__MOP__Attribute);
+    MOP_CALL_BOOT (boot_Class__MOP__Mixin__AttributeBase);
     MOP_CALL_BOOT (boot_Class__MOP__Method);
 
 # use prototype here to be compatible with get_code_info from Sub::Identify