Skip to content

Cross-references don't work in property's type annotations #84

@rowan-stein

Description

@rowan-stein

Cross-references don't work in property's type annotations

User report

A documented type in property's type annotation does not get cross-referenced:

from typing import Optional


class Point:
    """
    A class representing a point.

    Attributes:
        x: Position X.
        y: Position Y.
    """
    x: int
    y: int


class Square:
    """A class representing a square figure."""
    #: Square's start position (top-left corner).
    start: Point
    #: Square width.
    width: int
    #: Square height.
    height: int

    @property
    def end(self) -> Point:
        """Square's end position (bottom-right corner)."""
        return Point(self.start.x + self.width, self.start.y + self.height)


class Rectangle:
    """
    A class representing a square figure.

    Attributes:
        start: Rectangle's start position (top-left corner).
        width: Rectangle width.
        height: Rectangle width.
    """
    start: Point
    width: int
    height: int

    @property
    def end(self) -> Point:
        """Rectangle's end position (bottom-right corner)."""
        return Point(self.start.x + self.width, self.start.y + self.height)

How to reproduce

$ git clone https://github.com/jack1142/sphinx-issue-9585
$ cd sphinx-issue-9585
$ pip install sphinx
$ cd docs
$ make html
$ # open _build/html/index.html and see the issue

Expected behavior

Types in property annotations (e.g., def end(self) -> Point) should be cross-referenced like attributes/return types.

Observed failure

  • Generated HTML shows the type name (e.g., "Point") as plain text in the property signature; it is not a link.
  • No stack trace is emitted; this is a rendering/linking issue without build errors. Sphinx emits no warnings for the type in this scenario because the domain never creates a cross-reference for the property type.

Research specification (root cause and proposed fix)

Root cause

  • Autodoc emits a ":type:" option for properties when autodoc_typehints != 'none' (PropertyDocumenter uses stringify_typehint).
  • In the Python domain:
    • PyAttribute.handle_signature() parses the :type: string via _parse_annotation(typ, env) and creates pending_xref nodes, so attributes' types link correctly.
    • PyProperty.handle_signature() appends the :type: value as plain text using addnodes.desc_annotation(typ, ': ' + typ) and does not call _parse_annotation.
  • Because PyProperty bypasses _parse_annotation, it never creates cross-reference nodes for the property type.

Proposed change

  • File: sphinx/domains/python.py
  • Function: PyProperty.handle_signature()
  • When a type option is present, parse it via _parse_annotation(typ, self.env) and insert the returned nodes into the signature, mirroring PyAttribute’s logic.
    • Pseudo-diff:
      • Before: signode += addnodes.desc_annotation(typ, ': ' + typ)
      • After: annotations = _parse_annotation(typ, self.env); signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)

Notes and configs

  • Respects python_use_unqualified_type_names (modern) to show short labels while linking to fully qualified targets.
  • Works regardless of how stringify_typehint renders names; cross-reference creation happens in the domain layer.
  • autodoc_typehints='none' remains unchanged (no type emitted; no linking).

Acceptance criteria

  • Property type annotations are rendered as cross-references (links) like attributes.
  • Behavior adheres to python_use_unqualified_type_names.
  • No regressions for attributes, variables, or callable return types.

Test plan (to be implemented in PR)

  • Domain-level tests ensuring PyProperty parses and links types (including None and fully qualified names).
  • Autodoc integration tests showing @property def end(self) -> Point produces a linked type in the signature.
  • A minimal docs example demonstrating pre/post-fix behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions