diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb index c8bc98e6ae..656d464866 100644 --- a/lib/rdoc/parser/c.rb +++ b/lib/rdoc/parser/c.rb @@ -862,7 +862,7 @@ def handle_attr(var_name, attr_name, read, write) # named +class_name+ in +parent+ which was assigned to the C +var_name+. def handle_class_module(var_name, type, class_name, parent, in_module) - parent_name = @known_classes[parent] || parent + parent_name = @known_classes[parent] || const_get_name(parent) || parent if in_module enclosure = @classes[in_module] || @store.find_c_enclosure(in_module) @@ -917,6 +917,35 @@ def handle_class_module(var_name, type, class_name, parent, in_module) @store.add_c_enclosure var_name, cm end + ## + # Returns the name of the class or module the C variable +var_name+ was + # assigned from rb_const_get(under, rb_intern("Name")), or +nil+ + # if there is no such assignment, +under+ is unknown, or +var_name+ is + # assigned different constants in different places. + # + #: (String?) -> String? + def const_get_name(var_name) + return unless var_name + + names = @content.scan(%r% + \b#{Regexp.escape var_name}\s*= + \s*rb_const_get(?:_at)?\s*\( + \s*(\w+)\s*, + \s*rb_intern(?:_const)?\s*\(\s*"(\w+)"\s*\) + \s*\) + %x).uniq + + return unless names.length == 1 + + under, name = names.first + under_name = @known_classes[under] + + return unless under_name + return name if under_name == 'Object' + + "#{under_name}::#{name}" + end + ## # Adds constants. By providing some_value: at the start of the comment you # can override the C value of the comment to give a friendly definition. diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb index 3b934dbab0..0206c3b504 100644 --- a/lib/rdoc/store.rb +++ b/lib/rdoc/store.rb @@ -307,7 +307,8 @@ def cleanup_stale_contributions def resolve_c_superclasses @classes_hash.each_value do |klass| - if klass.superclass.is_a?(String) && (candidate = find_c_enclosure(klass.superclass)) + if klass.superclass.is_a?(String) && (candidate = find_c_enclosure(klass.superclass)) && + !candidate.equal?(klass) klass.superclass = candidate end end diff --git a/test/rdoc/parser/c_test.rb b/test/rdoc/parser/c_test.rb index c2b92ae053..40ff64a156 100644 --- a/test/rdoc/parser/c_test.rb +++ b/test/rdoc/parser/c_test.rb @@ -497,6 +497,47 @@ def test_do_classes_class_under_rb_path2class assert_equal 'this is Kernel::Foo < A::B', klass.comment.text end + def test_do_classes_class_under_rb_const_get + content = <<~C + VALUE mExample = rb_define_module("Example"); + VALUE cSocket = rb_const_get(rb_cObject, rb_intern("Socket")); + cSocket = rb_define_class_under(mExample, "Socket", cSocket); + C + + klass = util_get_class content, 'cSocket' + + assert_equal 'Example::Socket', klass.full_name + assert_equal 'Socket', klass.superclass + end + + def test_do_classes_class_rb_const_get_under_module + content = <<~C + VALUE mNet = rb_define_module("Net"); + VALUE cProtocol = rb_const_get(mNet, rb_intern("Protocol")); + VALUE cFoo = rb_define_class("Foo", cProtocol); + C + + klass = util_get_class content, 'cFoo' + + assert_equal 'Foo', klass.full_name + assert_equal 'Net::Protocol', klass.superclass + end + + def test_do_classes_class_rb_const_get_reassigned + content = <<~C + VALUE mA = rb_define_module("A"); + VALUE klass = rb_const_get(rb_cObject, rb_intern("IO")); + VALUE cX = rb_define_class_under(mA, "X", klass); + klass = rb_const_get(rb_cObject, rb_intern("String")); + VALUE cY = rb_define_class_under(mA, "Y", klass); + C + + klass = util_get_class content, 'cY' + + assert_equal 'A::Y', klass.full_name + assert_equal 'klass', klass.superclass + end + def test_do_classes_singleton content = <<~C VALUE cFoo = rb_define_class("Foo", rb_cObject); diff --git a/test/rdoc/rdoc_store_test.rb b/test/rdoc/rdoc_store_test.rb index 6caccdc7b5..3c0cdae298 100644 --- a/test/rdoc/rdoc_store_test.rb +++ b/test/rdoc/rdoc_store_test.rb @@ -299,6 +299,17 @@ def test_resolve_c_superclasses assert_equal(parent, @s.classes_hash['Child'].superclass) end + def test_resolve_c_superclasses_self + c_file = @s.add_file 'ext.c' + klass = c_file.add_class RDoc::NormalClass, 'Socket', 'cSocket' + + @s.add_c_enclosure('cSocket', klass) + + @s.resolve_c_superclasses + + assert_equal 'cSocket', @s.classes_hash['Socket'].superclass + end + def test_find_class_named assert_equal @c1, @store.find_class_named('C1')