Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #7812: autodoc: crashed when given name is conflicted #7914

Merged
merged 1 commit into from Jul 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -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
Expand Down
52 changes: 12 additions & 40 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -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
Expand Down Expand Up @@ -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]


Expand All @@ -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]


Expand Down
5 changes: 5 additions & 0 deletions 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
2 changes: 2 additions & 0 deletions tests/roots/test-ext-autodoc/target/name_conflict/foo.py
@@ -0,0 +1,2 @@
class bar:
"""docstring of target.name_conflict.foo::bar."""
43 changes: 33 additions & 10 deletions tests/test_ext_autodoc.py
Expand Up @@ -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'
Expand Down Expand Up @@ -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',
'',
Expand Down Expand Up @@ -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()',
Expand All @@ -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()',
Expand Down Expand Up @@ -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.',
'',
]
4 changes: 2 additions & 2 deletions tests/test_ext_autodoc_autofunction.py
Expand Up @@ -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.',
'',
Expand Down