diff --git a/lib/rdoc/code_object/class_module.rb b/lib/rdoc/code_object/class_module.rb index 76ded527a1..379b463dcd 100644 --- a/lib/rdoc/code_object/class_module.rb +++ b/lib/rdoc/code_object/class_module.rb @@ -174,6 +174,10 @@ def add_things(my_things, other_things) # :nodoc: # in the ancestors. The superclass, if any, comes last. def ancestors + included_ancestors + end + + def included_ancestors # :nodoc: includes.map { |i| i.module }.reverse end @@ -807,10 +811,17 @@ def superclass=(superclass) def super_classes result = [] + # Degenerate input can produce a cyclic superclass chain + visited = [full_name] parent = self while parent = parent.superclass + if parent.is_a?(String) + result << parent + break + end + break if visited.include?(parent.full_name) + visited << parent.full_name result << parent - return result if parent.is_a?(String) end result end diff --git a/lib/rdoc/code_object/normal_class.rb b/lib/rdoc/code_object/normal_class.rb index 6b68d6db56..6f648ea339 100644 --- a/lib/rdoc/code_object/normal_class.rb +++ b/lib/rdoc/code_object/normal_class.rb @@ -10,15 +10,12 @@ class RDoc::NormalClass < RDoc::ClassModule # RDoc::ClassModules and Strings. def ancestors - if String === superclass then - super << superclass - elsif superclass then - ancestors = super - ancestors << superclass - ancestors.concat superclass.ancestors - else - super + ancestors = included_ancestors + super_classes.each do |sclass| + ancestors << sclass + ancestors.concat sclass.included_ancestors unless String === sclass end + ancestors end def aref_prefix # :nodoc: diff --git a/test/rdoc/code_object/class_module_test.rb b/test/rdoc/code_object/class_module_test.rb index bff19a1513..e985db02a6 100644 --- a/test/rdoc/code_object/class_module_test.rb +++ b/test/rdoc/code_object/class_module_test.rb @@ -1358,6 +1358,23 @@ def test_super_classes assert_equal [rdoc_c3_h1, rdoc_object, "BasicObject"], @c3_h2.super_classes end + def test_super_classes_superclass_cycle + c1 = @top_level.add_class RDoc::NormalClass, 'Cycle1' + c2 = @top_level.add_class RDoc::NormalClass, 'Cycle2' + c1.superclass = c2 + c2.superclass = c1 + + assert_equal [c2], c1.super_classes + assert_equal [c1], c2.super_classes + end + + def test_super_classes_superclass_referencing_itself + klass = @top_level.add_class RDoc::NormalClass, 'Klass' + klass.superclass = klass + + assert_empty klass.super_classes + end + def test_update_aliases_class n1 = @xref_data.add_module RDoc::NormalClass, 'N1' n1_k2 = n1.add_module RDoc::NormalClass, 'N2' diff --git a/test/rdoc/code_object/normal_class_test.rb b/test/rdoc/code_object/normal_class_test.rb index a097619635..a3e18943c5 100644 --- a/test/rdoc/code_object/normal_class_test.rb +++ b/test/rdoc/code_object/normal_class_test.rb @@ -22,6 +22,43 @@ def test_ancestors_multilevel assert_equal [c2, c1, @object, 'BasicObject'], c3.ancestors end + def test_ancestors_superclass_cycle + c1 = @top_level.add_class RDoc::NormalClass, 'Cycle1' + c2 = @top_level.add_class RDoc::NormalClass, 'Cycle2' + c1.superclass = c2 + c2.superclass = c1 + + assert_equal [c2], c1.ancestors + assert_equal [c1], c2.ancestors + end + + def test_ancestors_superclass_referencing_itself + klass = @top_level.add_class RDoc::NormalClass, 'Klass' + incl = RDoc::Include.new 'Incl', '' + klass.add_include incl + klass.superclass = klass + + assert_equal [incl.name], klass.ancestors + end + + def test_ancestors_chain_ending_with_nil_superclass + base = @top_level.add_class RDoc::NormalClass, 'Base' + base.superclass = nil + sub = @top_level.add_class RDoc::NormalClass, 'Sub', 'Base' + + assert_equal [base], sub.ancestors + end + + def test_ancestors_superclass_is_module + klass = @top_level.add_class RDoc::NormalClass, 'Klass' + mod = @top_level.add_module RDoc::NormalModule, 'Mod' + klass.superclass = mod + + # A module is not registered as a class, so the superclass stays a String + assert_equal 'Mod', klass.superclass + assert_equal ['Mod'], klass.ancestors + end + def test_aref assert_equal 'class-c1', @c1.aref assert_equal 'class-c2-c3', @c2_c3.aref