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

Python domain considers all function/method/class argument annotations to be "class", when some are "data" #13308

Open
coretl opened this issue Feb 6, 2025 · 0 comments
Labels

Comments

@coretl
Copy link
Contributor

coretl commented Feb 6, 2025

Describe the bug

This is somewhat related to a number of issues in #11991, but is wider than just autodoc.

It also affects sphinx-autodoc2 users as TypeVars are created as py:data rather than py:class.

When creating an xref from an argument annotation, this is called:

if reftarget == 'None' or reftarget.startswith('typing.'):
# typing module provides non-class types. Obj reference is good to refer them.
reftype = 'obj'
else:
reftype = 'class'
return reftype, reftarget, title, refspecific

This means that if you write code like:

T = int | str

class Foo:
    def __init__(t: T): ...

then the python domain expects T to be a class, but it is categorized as data:

WARNING: py:class reference target not found: foo.T [ref.class]

One possible solution is to widen the reftype in parse_reftarget to "obj" in every case, not just for the typing module:

def parse_reftarget(
    reftarget: str, suppress_prefix: bool = False
) -> tuple[str, str, str, bool]:
    """Parse a type string and return (reftype, reftarget, title, refspecific flag)"""
    refspecific = False
    if reftarget.startswith('.'):
        reftarget = reftarget[1:]
        title = reftarget
        refspecific = True
    elif reftarget.startswith('~'):
        reftarget = reftarget[1:]
        title = reftarget.split('.')[-1]
    elif suppress_prefix:
        title = reftarget.split('.')[-1]
    elif reftarget.startswith('typing.'):
        title = reftarget[7:]
    else:
        title = reftarget

    return 'obj', reftarget, title, refspecific

This would fix it for my application, but would break applications that create an attribute with the same name as a builtin, so would probably need a new configuration variable.

Another possible solution would be to get sphinx-autodoc2 to make all TypeVars or unions reftype "class" rather than "data", although that may also be wrong.

I hope you don't mind me tagging you @chrisjsewell but maybe as author of sphinx-autodoc2 you may have an opinion?

How to Reproduce

index
=====

.. py:module:: foo

.. py:data:: T
   :value: TypeVar(...)

.. py:class:: Foo(t: T)
# conf.py
nitpicky = True

Environment Information

Platform:              linux; (Linux-4.18.0-513.24.1.el8_9.x86_64-x86_64-with-glibc2.28)
Python version:        3.11.5 (main, Sep 22 2023, 15:34:29) [GCC 8.5.0 20210514 (Red Hat 8.5.0-20)])
Python implementation: CPython
Sphinx version:        8.2.0+/1ab62c9b0
Docutils version:      0.21.2
Jinja2 version:        3.1.5
Pygments version:      2.19.1

Sphinx extensions

[]

Additional context

I am working around this problem with the following in conf.py:

from sphinx import addnodes, application, environment
from sphinx.ext import intersphinx

# A custom handler for TypeVars and Unions
def missing_reference_handler(
    app: application.Sphinx,
    env: environment.BuildEnvironment,
    node: addnodes.pending_xref,
    contnode,
):
    target = node["reftarget"]
    if "." in target and node["reftype"] == "class":
        # Try again as `obj` so we pick up Unions, TypeVars and other things
        if target.startswith("ophyd_async"):
            # Pick it up from our domain
            domain = env.domains[node["refdomain"]]
            refdoc = node.get("refdoc")
            return domain.resolve_xref(
                env, refdoc, app.builder, "obj", target, node, contnode
            )
        else:
            # pass it to intersphinx with the right type
            node["reftype"] = "obj"
            return intersphinx.missing_reference(app, env, node, contnode)


def setup(app: application.Sphinx):
    app.connect("missing-reference", missing_reference_handler)
@coretl coretl added the type:bug label Feb 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant