Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Contributors
* Ian Lee -- quickstart improvements
* Jacob Mason -- websupport library (GSOC project)
* James Addison -- linkcheck and HTML search improvements
* Jens H. Nielsen -- Small fixes for incorrect xref resolution
* Jeppe Pihl -- literalinclude improvements
* Jeremy Maitin-Shepard -- C++ domain improvements
* Joel Wurtz -- cellspanning support in LaTeX
Expand Down
9 changes: 5 additions & 4 deletions sphinx/domains/python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,11 +945,12 @@ def resolve_xref(
if not matches and type == 'class':
# fallback to data/attr (for type aliases)
# type aliases are documented as data/attr but referenced as class
matches = self.find_obj(env, modname, clsname, target, 'data', searchmode)
# Use searchmode=0 (exact match only) to avoid fuzzy matching
# that could incorrectly match class attributes (e.g. D.list)
# when resolving builtin type annotations (e.g. list[int]).
matches = self.find_obj(env, modname, clsname, target, 'data', 0)
if not matches:
matches = self.find_obj(
env, modname, clsname, target, 'attr', searchmode
)
matches = self.find_obj(env, modname, clsname, target, 'attr', 0)
if not matches and type == 'attr':
# fallback to meth (for property; Sphinx 2.4.x)
# this ensures that `:attr:` role continues to refer to the old property entry
Expand Down
Empty file.
28 changes: 28 additions & 0 deletions tests/roots/test-domain-py-xref-type-alias-builtin/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Builtin Type Alias False Positive Test
=======================================

This tests that builtin types like ``list`` in function signatures do not
incorrectly link to class attributes with the same name.


.. py:module:: mymodule

Module to test builtin type cross-reference false positives.


.. py:class:: MyClass

A class with an attribute that shadows a builtin type name.

.. py:attribute:: list
:value: [1, 2, 3]

An attribute named ``list`` that shadows the builtin.


.. py:function:: process(items: list) -> None
:module: mymodule

Process a list of items.

The ``list`` type annotation here should NOT link to ``MyClass.list``.
37 changes: 37 additions & 0 deletions tests/test_domains/test_domain_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -1900,3 +1900,40 @@ def test_type_alias_xref_resolution(app: SphinxTestApp) -> None:
'<a class="reference internal" href="#alias_module.HandlerType"'
in process_error_signature
), 'HandlerType type alias link not found in process_error function signature'


@pytest.mark.sphinx('html', testroot='domain-py-xref-type-alias-builtin')
def test_type_alias_xref_no_false_positive(app: SphinxTestApp) -> None:
"""Test that builtin type annotations don't falsely link to class attributes.

Regression test for a bug where ``def process(items: list)`` combined with
``class MyClass`` having a ``list`` attribute would cause the ``list`` type
annotation to incorrectly resolve to ``MyClass.list`` via the class→data/attr
fallback in PythonDomain.resolve_xref (searchmode must be 0 for exact match).
"""
app.build()

html_content = (app.outdir / 'index.html').read_text(encoding='utf8')

# The MyClass.list attribute should still be properly documented
assert 'id="mymodule.MyClass.list"' in html_content, (
'MyClass.list attribute anchor not found in HTML'
)

# Find the process() function signature
process_match = re.search(
r'<span class="pre">process</span>.*?</dt>', html_content, re.DOTALL
)
assert process_match is not None, 'Could not find process function signature'
process_signature = process_match.group(0)

# The critical assertion: ``list`` in the function signature must NOT link
# to MyClass.list. If the fallback used fuzzy matching (searchmode=1),
# "list" would match "MyClass.list" and produce a false-positive link.
assert (
'<a class="reference internal" href="#mymodule.MyClass.list"'
not in process_signature
), (
'list type annotation in process() signature incorrectly links to '
'MyClass.list — the class→data/attr fallback used fuzzy matching'
)
Loading