diff --git a/CHANGELOG.md b/CHANGELOG.md index 4087e174..2ffac0b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.22 - Allow Sphinx explicitly to write in parallel. +- Fixed crash when documenting ParamSpecArgs ## 1.21.7 diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 60705edd..af60b9ae 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -35,13 +35,25 @@ _TYPES_DICT[types.FunctionType] = "FunctionType" +def _get_types_type(obj: Any) -> str | None: + try: + return _TYPES_DICT.get(obj) + except Exception: + # e.g. exception: unhashable type + return None + + def get_annotation_module(annotation: Any) -> str: - if annotation in _TYPES_DICT: - return "types" if annotation is None: return "builtins" + if _get_types_type(annotation) is not None: + return "types" is_new_type = sys.version_info >= (3, 10) and isinstance(annotation, NewType) - if is_new_type or isinstance(annotation, TypeVar) or type(annotation).__name__ == "ParamSpec": + if ( + is_new_type + or isinstance(annotation, TypeVar) + or type(annotation).__name__ in ("ParamSpec", "ParamSpecArgs", "ParamSpecKwargs") + ): return "typing" if hasattr(annotation, "__module__"): return annotation.__module__ # type: ignore # deduced Any @@ -63,8 +75,9 @@ def get_annotation_class_name(annotation: Any, module: str) -> str: return "None" if annotation is AnyStr: return "AnyStr" - if annotation in _TYPES_DICT: - return _TYPES_DICT[annotation] + val = _get_types_type(annotation) + if val is not None: + return val if _is_newtype(annotation): return "NewType" diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index a25a6e6c..f9c3a0b0 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -58,6 +58,8 @@ S = TypeVar("S", bound="miss") # type: ignore # miss not defined on purpose # noqa: F821 W = NewType("W", str) P = typing_extensions.ParamSpec("P") +P_args = P.args # type:ignore[attr-defined] +P_kwargs = P.kwargs # type:ignore[attr-defined] P_co = typing_extensions.ParamSpec("P_co", covariant=True) # type: ignore P_contra = typing_extensions.ParamSpec("P_contra", contravariant=True) # type: ignore P_bound = typing_extensions.ParamSpec("P_bound", bound=str) # type: ignore @@ -163,6 +165,9 @@ def __getitem__(self, params): pytest.param(Match[str], "typing", "Match", (str,), id="Match_parametrized"), pytest.param(IO, "typing", "IO", (), id="IO"), pytest.param(W, "typing", "NewType", (str,), id="W"), + pytest.param(P, "typing", "ParamSpec", (), id="P"), + pytest.param(P_args, "typing", "ParamSpecArgs", (), id="P_args"), + pytest.param(P_kwargs, "typing", "ParamSpecKwargs", (), id="P_kwargs"), pytest.param(Metaclass, __name__, "Metaclass", (), id="Metaclass"), pytest.param(Slotted, __name__, "Slotted", (), id="Slotted"), pytest.param(A, __name__, "A", (), id="A"),