Fix one of the issues you see with metaclass incomat when you have a moose-non moose...
Tomas Doran (t0m) [Thu, 7 May 2009 00:07:25 +0000 (01:07 +0100)]
Changes
lib/Moose/Meta/Class.pm
t/060_compat/004_extends_nonmoose_that_isa_moose_with_metarole.t [moved from t/600_todo_tests/008_moose_nonmoose_moose_chain_extends.t with 59% similarity]

diff --git a/Changes b/Changes
index 10b2191..9cadfa7 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,6 +1,10 @@
 Also see Moose::Manual::Delta for more details of, and workarounds
 for, noteworthy changes.
 
+    * Moose::Meta::Class
+      - Fix metaclass incompatibility errors when extending a vanilla perl
+        class which isa Moose class with a metaclass role applied (t0m)
+
 0.78
     * Moose::Cookbook::FAQ and Moose::Cookbook::WTF
       - Merged these documents into what is now Moose::Manual::FAQ
index 09a0d33..b1c642d 100644 (file)
@@ -8,7 +8,7 @@ use Class::MOP;
 
 use Carp ();
 use List::Util qw( first );
-use List::MoreUtils qw( any all uniq );
+use List::MoreUtils qw( any all uniq first_index );
 use Scalar::Util 'weaken', 'blessed';
 
 our $VERSION   = '0.77';
@@ -305,24 +305,44 @@ sub _fix_metaclass_incompatibility {
     my ($self, @superclasses) = @_;
 
     foreach my $super (@superclasses) {
-        next if $self->_superclass_meta_is_compatible($super);
-
-        unless ( $self->is_pristine ) {
-            $self->throw_error(
-                      "Cannot attempt to reinitialize metaclass for "
-                    . $self->name
-                    . ", it isn't pristine" );
+        my $meta = Class::MOP::Class->initialize($super);
+
+        my @all_supers = $meta->linearized_isa;
+        shift(@all_supers); # Discard self
+        my @super_metas_to_fix = ( $meta );
+
+        # We need to check&fix the imediate superclass, and if its @ISA contains
+        # a class without a metaclass instance, followed by a class with a
+        # metaclass instance, init a metaclass instance for classes without
+        # one and fix compat up to and including the class which was already
+        # initialized.
+        my $idx = first_index { Class::MOP::class_of($_) } @all_supers;
+        push(@super_metas_to_fix,
+            map { Class::MOP::Class->initialize($_) } @all_supers[0..$idx]
+        ) if ($idx >= 0);
+
+        foreach my $super_meta (@super_metas_to_fix) {
+            $self->_fix_one_incompatible_metaclass($super_meta);
         }
+    }
+}
+
+sub _fix_one_incompatible_metaclass {
+    my ($self, $meta) = @_;
+
+    return if $self->_superclass_meta_is_compatible($meta);
 
-        $self->_reconcile_with_superclass_meta($super);
+    unless ( $self->is_pristine ) {
+        $self->throw_error(
+              "Cannot attempt to reinitialize metaclass for "
+            . $self->name
+            . ", it isn't pristine" );
     }
+    $self->_reconcile_with_superclass_meta($meta);
 }
 
 sub _superclass_meta_is_compatible {
-    my ($self, $super) = @_;
-
-    my $super_meta = Class::MOP::Class->initialize($super)
-        or return 1;
+    my ($self, $super_meta) = @_;
 
     next unless $super_meta->isa("Class::MOP::Class");
 
@@ -348,9 +368,7 @@ my @MetaClassTypes =
         error_class );
 
 sub _reconcile_with_superclass_meta {
-    my ($self, $super) = @_;
-
-    my $super_meta = Class::MOP::class_of($super);
+    my ($self, $super_meta) = @_;
 
     my $super_meta_name
         = $super_meta->is_immutable
@@ -19,11 +19,8 @@ use Test::More tests => 1;
     use Moose;
     use Test::More;
     use Test::Exception;
-    TODO: {
-        local $TODO = 'Metaclass incompatibility';
-        lives_ok {
-            extends 'SubClassUseBase';
-        } 'Can extend non-moose class whos parent class is a Moose class with a meta role';
-    }
+    lives_ok {
+        extends 'SubClassUseBase';
+    } 'Can extend non-moose class whos parent class is a Moose class with a meta role';
 }