Skip to content

Commit e7e1db6

Browse files
authored
Improve checking of "__slots__" (#12531)
Don't rely on `object.__slots__` being defined, since it's not defined at runtime (and currently it's removed in typeshed).
1 parent 07d8878 commit e7e1db6

File tree

4 files changed

+21
-3
lines changed

4 files changed

+21
-3
lines changed

mypy/checker.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,17 @@ def check_setattr_method(self, typ: Type, context: Context) -> None:
14391439
if not is_subtype(typ, method_type):
14401440
self.msg.invalid_signature_for_special_method(typ, context, '__setattr__')
14411441

1442+
def check_slots_definition(self, typ: Type, context: Context) -> None:
1443+
"""Check the type of __slots__."""
1444+
str_type = self.named_type("builtins.str")
1445+
expected_type = UnionType([str_type,
1446+
self.named_generic_type("typing.Iterable", [str_type])])
1447+
self.check_subtype(typ, expected_type, context,
1448+
message_registry.INVALID_TYPE_FOR_SLOTS,
1449+
'actual type',
1450+
'expected type',
1451+
code=codes.ASSIGNMENT)
1452+
14421453
def check_match_args(self, var: Var, typ: Type, context: Context) -> None:
14431454
"""Check that __match_args__ contains literal strings"""
14441455
typ = get_proper_type(typ)
@@ -2281,6 +2292,9 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
22812292
else:
22822293
self.check_getattr_method(signature, lvalue, name)
22832294

2295+
if name == '__slots__':
2296+
typ = lvalue_type or self.expr_checker.accept(rvalue)
2297+
self.check_slots_definition(typ, lvalue)
22842298
if name == '__match_args__' and inferred is not None:
22852299
typ = self.expr_checker.accept(rvalue)
22862300
self.check_match_args(inferred, typ, lvalue)

mypy/message_registry.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage":
6060
'Incompatible types in "async with" for "__aexit__"'
6161
)
6262
INCOMPATIBLE_TYPES_IN_ASYNC_FOR: Final = 'Incompatible types in "async for"'
63+
INVALID_TYPE_FOR_SLOTS: Final = 'Invalid type for "__slots__"'
6364

6465
ASYNC_FOR_OUTSIDE_COROUTINE: Final = '"async for" outside async function'
6566
ASYNC_WITH_OUTSIDE_COROUTINE: Final = '"async with" outside async function'

mypy/typeshed/stdlib/builtins.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ class _SupportsAiter(Protocol[_T_co]):
8585
class object:
8686
__doc__: str | None
8787
__dict__: dict[str, Any]
88-
__slots__: str | Iterable[str]
8988
__module__: str
9089
__annotations__: dict[str, Any]
9190
@property

test-data/unit/pythoneval.test

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,13 +1312,17 @@ f(E)
13121312
g(E)
13131313

13141314
[case testInvalidSlots]
1315+
from typing import List
13151316
class A:
13161317
__slots__ = 1
13171318
class B:
13181319
__slots__ = (1, 2)
1320+
class C:
1321+
__slots__: List[int] = []
13191322
[out]
1320-
_testInvalidSlots.py:2: error: Incompatible types in assignment (expression has type "int", base class "object" defined the type as "Union[str, Iterable[str]]")
1321-
_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]]")
1323+
_testInvalidSlots.py:3: error: Invalid type for "__slots__" (actual type "int", expected type "Union[str, Iterable[str]]")
1324+
_testInvalidSlots.py:5: error: Invalid type for "__slots__" (actual type "Tuple[int, int]", expected type "Union[str, Iterable[str]]")
1325+
_testInvalidSlots.py:7: error: Invalid type for "__slots__" (actual type "List[int]", expected type "Union[str, Iterable[str]]")
13221326

13231327
[case testDictWithStarStarSpecialCase]
13241328
from typing import Dict

0 commit comments

Comments
 (0)