handle non-Moo superclass constructors
Matt S Trout [Mon, 6 Dec 2010 21:36:30 +0000 (21:36 +0000)]
Changes
lib/Method/Generate/Constructor.pm
lib/Moo.pm
t/extends-non-moo.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 7f14e19..4f3d3b0 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,3 +1,5 @@
+  - Automatic detection on non-Moo superclasses
+
 0.9.3 Sun Dec 5 2010
   - Fix _load_module to deal with pre-existing subpackages
 
index 2360006..c7138a1 100644 (file)
@@ -20,6 +20,11 @@ sub accessor_generator {
   $_[0]->{accessor_generator}
 }
 
+sub construction_string {
+  my ($self) = @_;
+  $self->{construction_string} or 'bless({}, $class);'
+}
+
 sub install_delayed {
   my ($self) = @_;
   my $package = $self->{package};
@@ -40,7 +45,7 @@ sub generate_method {
   my $body = '    my $class = shift;'."\n";
   $body .= $self->_generate_args;
   $body .= $self->_check_required($spec);
-  $body .= '    my $new = bless({}, $class);'."\n";
+  $body .= '    my $new = '.$self->construction_string.";\n";
   $body .= $self->_assign_new($spec);
   if ($into->can('BUILD')) {
     require Method::Generate::BuildAll;
index 571415e..badb96d 100644 (file)
@@ -52,27 +52,32 @@ sub _constructor_maker_for {
   return unless $MAKERS{$target};
   $MAKERS{$target}{constructor} ||= do {
     require Method::Generate::Constructor;
+    my $con;
+
+    # using the -last- entry in @ISA means that classes created by
+    # Role::Tiny as N roles + superclass will still get the attributes
+    # from the superclass via the ->register_attribute_specs call later
+
+    if (my $super = do { no strict 'refs'; ${"${target}::ISA"}[-1] }) {
+      $con = $MAKERS{$super}{constructor} if $MAKERS{$super};
+    }
+    my $moo_constructor = !!$con || do {
+      my $t_new = $target->can('new');
+      $t_new and $t_new == Moo::Object->can('new');
+    };
+    require Moo::_mro unless $moo_constructor;
     Method::Generate::Constructor
       ->new(
         package => $target,
         accessor_generator => do {
           require Method::Generate::Accessor;
           Method::Generate::Accessor->new;
-        }
+        },
+        ($moo_constructor ? ()
+          : (construction_string => '$class->next::method(@_)'))
       )
       ->install_delayed
-      ->register_attribute_specs(do {
-        my @spec;
-        # using the -last- entry in @ISA means that classes created by
-        # Role::Tiny as N roles + superclass will still get the attributes
-        # from the superclass
-        if (my $super = do { no strict 'refs'; ${"${target}::ISA"}[-1] }) {
-          if (my $con = $MAKERS{$super}{constructor}) {
-            @spec = %{$con->all_attribute_specs};
-          }
-        }
-        @spec;
-      });
+      ->register_attribute_specs(%{$con?$con->all_attribute_specs:{}})
   }
 }
 
diff --git a/t/extends-non-moo.t b/t/extends-non-moo.t
new file mode 100644 (file)
index 0000000..9c6beb1
--- /dev/null
@@ -0,0 +1,56 @@
+use strictures 1;
+use Test::More;
+
+{
+    package t::moo::extends_non_moo::base;
+
+    sub new {
+        my ($proto, $args) = @_;
+        bless $args, $proto;
+    }
+
+    sub to_app {
+        (shift)->{app};
+    }
+
+    package t::moo::extends_non_moo::middle;
+    use base qw(t::moo::extends_non_moo::base);
+
+    sub wrap {
+        my($class, $app) = @_;
+        $class->new({app => $app})
+              ->to_app;
+    }
+    package t::moo::extends_non_moo::moo;
+    use Moo;
+    extends 't::moo::extends_non_moo::middle';
+
+    package t::moo::extends_non_moo::moo_with_attr;
+    use Moo;
+    extends 't::moo::extends_non_moo::middle';
+    has 'attr' => (is=>'ro');
+}
+
+ok my $app = 100,
+  'prepared $app';
+
+ok $app = t::moo::extends_non_moo::middle->wrap($app),
+  '$app from $app';
+
+is $app, 100,
+  '$app still 100';
+
+ok $app = t::moo::extends_non_moo::moo->wrap($app),
+  '$app from $app';
+
+is $app, 100,
+  '$app still 100';
+
+ok $app = t::moo::extends_non_moo::moo_with_attr->wrap($app),
+  '$app from $app';
+
+is $app, 100,
+  '$app still 100';
+
+done_testing();