diff --git a/CHANGES b/CHANGES index 76dcd673dc8..985116851d1 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Bugs fixed the autoclass directive * #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints is 'description' +* #7812: autodoc: crashed if the target name matches to both an attribute and + module that are same name * #7812: autosummary: generates broken stub files if the target code contains an attribute and module that are same name * #7806: viewcode: Failed to resolve viewcode references on 3rd party builders diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index bb89920bee7..85bea8c4336 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -32,7 +32,6 @@ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import inspect from sphinx.util import logging -from sphinx.util import split_full_qualified_name from sphinx.util.docstrings import extract_metadata, prepare_docstring from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature from sphinx.util.typing import stringify as stringify_typehint @@ -980,31 +979,15 @@ def resolve_name(self, modname: str, parents: Any, path: str, base: Any ) -> Tuple[str, List[str]]: if modname is None: if path: - stripped = path.rstrip('.') - modname, qualname = split_full_qualified_name(stripped) - if modname is None: - # if documenting a toplevel object without explicit module, - # it can be contained in another auto directive ... - modname = self.env.temp_data.get('autodoc:module') - # ... or in the scope of a module directive - if not modname: - modname = self.env.ref_context.get('py:module') - - if modname: - stripped = ".".join([modname, stripped]) - modname, qualname = split_full_qualified_name(stripped) - - if qualname: - parents = qualname.split(".") - else: - parents = [] + modname = path.rstrip('.') else: + # if documenting a toplevel object without explicit module, + # it can be contained in another auto directive ... modname = self.env.temp_data.get('autodoc:module') - parents = [] - + # ... or in the scope of a module directive if not modname: modname = self.env.ref_context.get('py:module') - + # ... else, it stays None, which means invalid return modname, parents + [base] @@ -1030,25 +1013,14 @@ def resolve_name(self, modname: str, parents: Any, path: str, base: Any # ... if still None, there's no way to know if mod_cls is None: return None, [] - - modname, qualname = split_full_qualified_name(mod_cls) - if modname is None: - # if documenting a toplevel object without explicit module, - # it can be contained in another auto directive ... + modname, sep, cls = mod_cls.rpartition('.') + parents = [cls] + # if the module name is still missing, get it like above + if not modname: modname = self.env.temp_data.get('autodoc:module') - # ... or in the scope of a module directive - if not modname: - modname = self.env.ref_context.get('py:module') - - if modname: - stripped = ".".join([modname, mod_cls]) - modname, qualname = split_full_qualified_name(stripped) - - if qualname: - parents = qualname.split(".") - else: - parents = [] - + if not modname: + modname = self.env.ref_context.get('py:module') + # ... else, it stays None, which means invalid return modname, parents + [base] diff --git a/tests/roots/test-ext-autodoc/target/name_conflict/__init__.py b/tests/roots/test-ext-autodoc/target/name_conflict/__init__.py new file mode 100644 index 00000000000..7ad521fc25a --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/name_conflict/__init__.py @@ -0,0 +1,5 @@ +from .foo import bar + +class foo: + """docstring of target.name_conflict::foo.""" + pass diff --git a/tests/roots/test-ext-autodoc/target/name_conflict/foo.py b/tests/roots/test-ext-autodoc/target/name_conflict/foo.py new file mode 100644 index 00000000000..bb83ca0d4b7 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/name_conflict/foo.py @@ -0,0 +1,2 @@ +class bar: + """docstring of target.name_conflict.foo::bar.""" diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 8c34c4e9fff..652c7f10bca 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -121,8 +121,8 @@ def verify(objtype, name, result): verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None)) # for members - directive.env.ref_context['py:module'] = 'sphinx.testing' - verify('method', 'util.SphinxTestApp.cleanup', + directive.env.ref_context['py:module'] = 'sphinx.testing.util' + verify('method', 'SphinxTestApp.cleanup', ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)) directive.env.ref_context['py:module'] = 'sphinx.testing.util' directive.env.ref_context['py:class'] = 'Foo' @@ -801,14 +801,14 @@ def test_autodoc_inner_class(app): actual = do_autodoc(app, 'class', 'target.Outer.Inner', options) assert list(actual) == [ '', - '.. py:class:: Outer.Inner()', - ' :module: target', + '.. py:class:: Inner()', + ' :module: target.Outer', '', ' Foo', '', '', - ' .. py:method:: Outer.Inner.meth()', - ' :module: target', + ' .. py:method:: Inner.meth()', + ' :module: target.Outer', '', ' Foo', '', @@ -1884,8 +1884,8 @@ def test_overload(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_pymodule_for_ModuleLevelDocumenter(app): - app.env.ref_context['py:module'] = 'target' - actual = do_autodoc(app, 'class', 'classes.Foo') + app.env.ref_context['py:module'] = 'target.classes' + actual = do_autodoc(app, 'class', 'Foo') assert list(actual) == [ '', '.. py:class:: Foo()', @@ -1896,8 +1896,8 @@ def test_pymodule_for_ModuleLevelDocumenter(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_pymodule_for_ClassLevelDocumenter(app): - app.env.ref_context['py:module'] = 'target' - actual = do_autodoc(app, 'method', 'methods.Base.meth') + app.env.ref_context['py:module'] = 'target.methods' + actual = do_autodoc(app, 'method', 'Base.meth') assert list(actual) == [ '', '.. py:method:: Base.meth()', @@ -1937,3 +1937,26 @@ def test_autodoc(app, status, warning): alias of bug2437.autodoc_dummy_foo.Foo""" assert warning.getvalue() == '' + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_name_conflict(app): + actual = do_autodoc(app, 'class', 'target.name_conflict.foo') + assert list(actual) == [ + '', + '.. py:class:: foo()', + ' :module: target.name_conflict', + '', + ' docstring of target.name_conflict::foo.', + '', + ] + + actual = do_autodoc(app, 'class', 'target.name_conflict.foo.bar') + assert list(actual) == [ + '', + '.. py:class:: bar()', + ' :module: target.name_conflict.foo', + '', + ' docstring of target.name_conflict.foo::bar.', + '', + ] diff --git a/tests/test_ext_autodoc_autofunction.py b/tests/test_ext_autodoc_autofunction.py index 579ad9f488e..da090d83a90 100644 --- a/tests/test_ext_autodoc_autofunction.py +++ b/tests/test_ext_autodoc_autofunction.py @@ -85,8 +85,8 @@ def test_methoddescriptor(app): actual = do_autodoc(app, 'function', 'builtins.int.__add__') assert list(actual) == [ '', - '.. py:function:: int.__add__(self, value, /)', - ' :module: builtins', + '.. py:function:: __add__(self, value, /)', + ' :module: builtins.int', '', ' Return self+value.', '',