diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index dedfdbe..e621592 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -36,7 +36,23 @@ from sphinx.ext.autodoc import Options _LOGGER = logging.getLogger(__name__) -_PYDATA_ANNOTATIONS = {"Any", "AnyStr", "Callable", "ClassVar", "Literal", "NoReturn", "Optional", "Tuple", "Union"} +_PYDATA_ANNOTS_TYPING = {"Any", "AnyStr", "Callable", "ClassVar", "Literal", "NoReturn", "Optional", "Tuple", "Union"} +_PYDATA_ANNOTS_TYPES = { + *("AsyncGeneratorType", "BuiltinFunctionType", "BuiltinMethodType"), + *("CellType", "ClassMethodDescriptorType", "CoroutineType"), + "EllipsisType", + *("FrameType", "FunctionType"), + *("GeneratorType", "GetSetDescriptorType"), + "LambdaType", + *("MemberDescriptorType", "MethodDescriptorType", "MethodType", "MethodWrapperType"), + # NoneType is special, but included here for completeness' sake + *("NoneType", "NotImplementedType"), + "WrapperDescriptorType", +} +_PYDATA_ANNOTATIONS = { + *(("typing", n) for n in _PYDATA_ANNOTS_TYPING), + *(("types", n) for n in _PYDATA_ANNOTS_TYPES), +} # types has a bunch of things like ModuleType where ModuleType.__module__ is # "builtins" and ModuleType.__name__ is "module", so we have to check for this. @@ -219,7 +235,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL full_name = f"{module}.{class_name}" if module != "builtins" else class_name fully_qualified: bool = getattr(config, "typehints_fully_qualified", False) prefix = "" if fully_qualified or full_name == class_name else "~" - role = "data" if module == "typing" and class_name in _PYDATA_ANNOTATIONS else "class" + role = "data" if (module, class_name) in _PYDATA_ANNOTATIONS else "class" args_format = "\\[{}]" formatted_args: str | None = "" diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index 92c96f4..65c3f6c 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -9,7 +9,7 @@ from io import StringIO from pathlib import Path from textwrap import dedent, indent -from types import FunctionType, ModuleType +from types import EllipsisType, FrameType, FunctionType, ModuleType, NotImplementedType, TracebackType from typing import ( # noqa: UP035 IO, Any, @@ -168,8 +168,12 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t pytest.param(str, ":py:class:`str`", id="str"), pytest.param(int, ":py:class:`int`", id="int"), pytest.param(StringIO, ":py:class:`~io.StringIO`", id="StringIO"), - pytest.param(FunctionType, ":py:class:`~types.FunctionType`", id="FunctionType"), + pytest.param(EllipsisType, ":py:data:`~types.EllipsisType`", id="EllipsisType"), + pytest.param(FunctionType, ":py:data:`~types.FunctionType`", id="FunctionType"), + pytest.param(FrameType, ":py:data:`~types.FrameType`", id="FrameType"), pytest.param(ModuleType, ":py:class:`~types.ModuleType`", id="ModuleType"), + pytest.param(NotImplementedType, ":py:data:`~types.NotImplementedType`", id="NotImplementedType"), + pytest.param(TracebackType, ":py:class:`~types.TracebackType`", id="TracebackType"), pytest.param(type(None), ":py:obj:`None`", id="type None"), pytest.param(type, ":py:class:`type`", id="type"), pytest.param(Callable, ":py:class:`~collections.abc.Callable`", id="abc-Callable"), @@ -414,7 +418,7 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str assert format_annotation(annotation, conf) == expected_result # Test for the correct role (class vs data) using the official Sphinx inventory - if "typing" in expected_result: + if any(modname in expected_result for modname in ("typing", "types")): m = re.match(r"^:py:(?Pclass|data|func):`~(?P[^`]+)`", result) assert m, "No match" name = m.group("name")