From 5bb2358e063e1f10a267d671d298866521fcc37f Mon Sep 17 00:00:00 2001 From: Mehdi Drissi Date: Sun, 31 Jul 2022 12:17:32 -0700 Subject: [PATCH] Add support for recursive type hints (#248) Co-authored-by: Mehdi Drissi --- CHANGELOG.md | 4 ++++ src/sphinx_autodoc_typehints/__init__.py | 15 +-------------- tests/test_sphinx_autodoc_typehints.py | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37cdc1fc..cabaf150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.19.1 + +- Fix bug for recursive type alias. + ## 1.19.0 - Support for CPython 3.11, no longer adds `Optional` when the argument is default per diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 4cddf21f..cbcfe91d 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -5,7 +5,6 @@ import sys import textwrap from ast import FunctionDef, Module, stmt -from typing import _eval_type # type: ignore # no import defined in stubs from typing import Any, AnyStr, Callable, ForwardRef, NewType, TypeVar, get_type_hints from sphinx.application import Sphinx @@ -120,8 +119,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901 # t # Special cases if isinstance(annotation, ForwardRef): - value = _resolve_forward_ref(annotation, config) - return format_annotation(value, config) + return annotation.__forward_arg__ if annotation is None or annotation is type(None): # noqa: E721 return ":py:obj:`None`" if annotation is Ellipsis: @@ -195,17 +193,6 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901 # t return result -def _resolve_forward_ref(annotation: ForwardRef, config: Config) -> Any: - raw, base_globals = annotation.__forward_arg__, config._annotation_globals - params = {"is_class": True} if (3, 10) > sys.version_info >= (3, 9, 8) or sys.version_info >= (3, 10, 1) else {} - value = ForwardRef(raw, is_argument=False, **params) - try: - result = _eval_type(value, base_globals, None) - except NameError: - result = raw # fallback to the value itself as string - return result - - # reference: https://github.com/pytorch/pytorch/pull/46548/files def normalize_source_lines(source_lines: str) -> str: """ diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index b03ae2c9..930d03bd 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -15,6 +15,7 @@ Callable, Dict, Generic, + List, Mapping, Match, NewType, @@ -55,6 +56,12 @@ S = TypeVar("S", bound="miss") # type: ignore # miss not defined on purpose # noqa: F821 W = NewType("W", str) +# Mypy does not support recursive type aliases, but +# other type checkers do. +RecList = Union[int, List["RecList"]] # type: ignore +MutualRecA = Union[bool, List["MutualRecB"]] # type: ignore +MutualRecB = Union[str, List["MutualRecA"]] # type: ignore + class A: def get_type(self) -> type: @@ -228,7 +235,7 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t (V, ":py:class:`~typing.TypeVar`\\(``V``, contravariant=True)"), (X, ":py:class:`~typing.TypeVar`\\(``X``, :py:class:`str`, :py:class:`int`)"), (Y, ":py:class:`~typing.TypeVar`\\(``Y``, bound= :py:class:`str`)"), - (Z, ":py:class:`~typing.TypeVar`\\(``Z``, bound= :py:class:`~test_sphinx_autodoc_typehints.A`)"), + (Z, ":py:class:`~typing.TypeVar`\\(``Z``, bound= A)"), (S, ":py:class:`~typing.TypeVar`\\(``S``, bound= miss)"), # ## These test for correct internal tuple rendering, even if not all are valid Tuple types # Zero-length tuple remains @@ -279,6 +286,14 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t "...], :py:class:`~numpy.float64`]" ), ), + ( + RecList, + (":py:data:`~typing.Union`\\[:py:class:`int`, :py:class:`~typing.List`\\[RecList]]"), + ), + ( + MutualRecA, + (":py:data:`~typing.Union`\\[:py:class:`bool`, :py:class:`~typing.List`\\[MutualRecB]]"), + ), ], ) def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str) -> None: