diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index 1815197..c6fd657 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -11,6 +11,8 @@ This library adheres to ``typing.no_type_check()`` (PR by @jolaf) - Fixed checking of variable assignments involving tuple unpacking (`#486 `_) +- Fixed ``TypeError`` when checking a class against ``type[Self]`` + (`#481 `_) **4.4.0** (2024-10-27) diff --git a/src/typeguard/_checkers.py b/src/typeguard/_checkers.py index fa7df9f..8166bf2 100644 --- a/src/typeguard/_checkers.py +++ b/src/typeguard/_checkers.py @@ -464,6 +464,8 @@ def check_class( if expected_class is Any: return + elif expected_class is typing_extensions.Self: + check_self(value, get_origin(expected_class), get_args(expected_class), memo) elif getattr(expected_class, "_is_protocol", False): check_protocol(value, expected_class, (), memo) elif isinstance(expected_class, TypeVar): @@ -847,8 +849,7 @@ def check_self( if isclass(value): if not issubclass(value, memo.self_type): raise TypeCheckError( - f"is not an instance of the self type " - f"({qualified_name(memo.self_type)})" + f"is not a subclass of the self type ({qualified_name(memo.self_type)})" ) elif not isinstance(value, memo.self_type): raise TypeCheckError( diff --git a/tests/test_checkers.py b/tests/test_checkers.py index dba7a7a..d2bcd81 100644 --- a/tests/test_checkers.py +++ b/tests/test_checkers.py @@ -36,6 +36,7 @@ ) import pytest +from typing_extensions import LiteralString from typeguard import ( CollectionCheckStrategy, @@ -64,12 +65,9 @@ ) if sys.version_info >= (3, 11): - from typing import LiteralString - SubclassableAny = Any else: from typing_extensions import Any as SubclassableAny - from typing_extensions import LiteralString if sys.version_info >= (3, 10): from typing import Concatenate, ParamSpec, TypeGuard diff --git a/tests/test_typechecked.py b/tests/test_typechecked.py index fd59230..d56f3ae 100644 --- a/tests/test_typechecked.py +++ b/tests/test_typechecked.py @@ -456,6 +456,29 @@ def method(cls, another: Self) -> None: rf"test_classmethod_arg_invalid\.\.Foo\)" ) + def test_self_type_valid(self): + class Foo: + @typechecked + def method(cls, subclass: type[Self]) -> None: + pass + + class Bar(Foo): + pass + + Foo().method(Bar) + + def test_self_type_invalid(self): + class Foo: + @typechecked + def method(cls, subclass: type[Self]) -> None: + pass + + pytest.raises(TypeCheckError, Foo().method, int).match( + rf'argument "subclass" \(class int\) is not a subclass of the self type ' + rf"\({__name__}\.{self.__class__.__name__}\." + rf"test_self_type_invalid\.\.Foo\)" + ) + class TestMock: def test_mock_argument(self):