From ed866f2b15efda5f1642fa9caed4580df4ff716c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 6 Apr 2022 14:51:03 +0100 Subject: [PATCH] Improve checking of "__slots__" Don't rely on `object.__slots__` being defined, since it's not defined at runtime (and currently it's removed in typeshed). --- mypy/checker.py | 14 ++++++++++++++ mypy/message_registry.py | 1 + mypy/typeshed/stdlib/builtins.pyi | 1 - test-data/unit/pythoneval.test | 8 ++++++-- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index a02a877a808f..436a27ace3f2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1439,6 +1439,17 @@ def check_setattr_method(self, typ: Type, context: Context) -> None: if not is_subtype(typ, method_type): self.msg.invalid_signature_for_special_method(typ, context, '__setattr__') + def check_slots_definition(self, typ: Type, context: Context) -> None: + """Check the type of __slots__.""" + str_type = self.named_type("builtins.str") + expected_type = UnionType([str_type, + self.named_generic_type("typing.Iterable", [str_type])]) + self.check_subtype(typ, expected_type, context, + message_registry.INVALID_TYPE_FOR_SLOTS, + 'actual type', + 'expected type', + code=codes.ASSIGNMENT) + def check_match_args(self, var: Var, typ: Type, context: Context) -> None: """Check that __match_args__ contains literal strings""" typ = get_proper_type(typ) @@ -2281,6 +2292,9 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type else: self.check_getattr_method(signature, lvalue, name) + if name == '__slots__': + typ = lvalue_type or self.expr_checker.accept(rvalue) + self.check_slots_definition(typ, lvalue) if name == '__match_args__' and inferred is not None: typ = self.expr_checker.accept(rvalue) self.check_match_args(inferred, typ, lvalue) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 8b5452d5753d..0f14d706ccca 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -60,6 +60,7 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": 'Incompatible types in "async with" for "__aexit__"' ) INCOMPATIBLE_TYPES_IN_ASYNC_FOR: Final = 'Incompatible types in "async for"' +INVALID_TYPE_FOR_SLOTS: Final = 'Invalid type for "__slots__"' ASYNC_FOR_OUTSIDE_COROUTINE: Final = '"async for" outside async function' ASYNC_WITH_OUTSIDE_COROUTINE: Final = '"async with" outside async function' diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi index 5547afc217f6..b38d645ef9fc 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi @@ -85,7 +85,6 @@ class _SupportsAiter(Protocol[_T_co]): class object: __doc__: str | None __dict__: dict[str, Any] - __slots__: str | Iterable[str] __module__: str __annotations__: dict[str, Any] @property diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index a0b98d2501c7..bba2885bf77b 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1312,13 +1312,17 @@ f(E) g(E) [case testInvalidSlots] +from typing import List class A: __slots__ = 1 class B: __slots__ = (1, 2) +class C: + __slots__: List[int] = [] [out] -_testInvalidSlots.py:2: error: Incompatible types in assignment (expression has type "int", base class "object" defined the type as "Union[str, Iterable[str]]") -_testInvalidSlots.py:4: error: Incompatible types in assignment (expression has type "Tuple[int, int]", base class "object" defined the type as "Union[str, Iterable[str]]") +_testInvalidSlots.py:3: error: Invalid type for "__slots__" (actual type "int", expected type "Union[str, Iterable[str]]") +_testInvalidSlots.py:5: error: Invalid type for "__slots__" (actual type "Tuple[int, int]", expected type "Union[str, Iterable[str]]") +_testInvalidSlots.py:7: error: Invalid type for "__slots__" (actual type "List[int]", expected type "Union[str, Iterable[str]]") [case testDictWithStarStarSpecialCase] from typing import Dict