Fix describe_class_methods on non-mergeable DFS mro
Peter Rabbitson [Tue, 14 Jun 2016 16:05:51 +0000 (18:05 +0200)]
Instead of trying to deduplicate - simply track which methods are locally
defined, and use that info combined with a reverse ISA-per-select-MRO to
build the final stack.

As a result the code is even more efficient and can now deal with real-life
insane hierarchies like:

https://metacpan.org/source/MJFLICK/DBIx-Class-Bootstrap-Simple-0.03/lib/DBIx/Class/Bootstrap/Simple.pm#L93

lib/DBIx/Class/_Util.pm
xt/extra/internals/attributes.t

index 371db28..35d11df 100644 (file)
@@ -699,15 +699,15 @@ sub modver_gt_or_eq_and_lt ($$$) {
 
           and
 
-        # on complex MI herarchies the method can be anywhere in the
-        # shadow stack - look through the entire slot, not just [0]
-        ( ! grep {
-          refaddr($_) == $current_node_refaddr
-        } @{ $slot->{methods}{ $_->{name} } || [] } )
+        unshift @{ $slot->{methods}{$_->{name}} }, $_
 
           and
 
-        unshift @{ $slot->{methods}{$_->{name}} }, $_
+        (
+          $_->{via_class} ne $class
+            or
+          $slot->{methods_defined_in_class}{$_->{name}} = $_
+        )
 
           and
 
@@ -720,9 +720,9 @@ sub modver_gt_or_eq_and_lt ($$$) {
       ) for (
 
         # what describe_class_methods for @my_ISA produced above
-        ( map { $_->[0] } map {
-          values %{ $describe_class_query_cache->{$_}{methods} }
-        } reverse @my_ISA ),
+        ( map { values %{
+          $describe_class_query_cache->{$_}{methods_defined_in_class} || {}
+        } } reverse @my_ISA ),
 
         # our own non-cleaned subs + their attributes
         ( map {
index b26f5d5..bed8efd 100644 (file)
@@ -390,6 +390,9 @@ sub add_more_attrs {
   $expected_desc->{methods_with_supers}{VALID_DBIC_CODE_ATTRIBUTE}
     = $expected_desc->{methods}{VALID_DBIC_CODE_ATTRIBUTE};
 
+  $expected_desc->{methods_defined_in_class}{attr}
+    = $expected_desc->{methods}{attr}[0];
+
   is_deeply (
     describe_class_methods("DBICTest::AttrTest"),
     $expected_desc,
@@ -441,4 +444,27 @@ else { SKIP: {
   );
 }}
 
+# this doesn't really belong in this test, but screw it
+{
+  package DBICTest::WackyDFS;
+  use base qw( DBICTest::SomeGrandParentClass DBICTest::SomeParentClass );
+}
+
+is_deeply
+  describe_class_methods("DBICTest::WackyDFS")->{methods}{VALID_DBIC_CODE_ATTRIBUTE},
+  [
+    {
+      attributes => {},
+      name => "VALID_DBIC_CODE_ATTRIBUTE",
+      via_class => "DBICTest::SomeGrandParentClass",
+    },
+    {
+      attributes => {},
+      name => "VALID_DBIC_CODE_ATTRIBUTE",
+      via_class => "DBIx::Class::MethodAttributes"
+    },
+  ],
+  'Expected description on unusable inheritance hierarchy'
+;
+
 done_testing;