diff --git a/mypy/semanal.py b/mypy/semanal.py index c3b9f8e918179..51a3becad23f4 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2320,8 +2320,8 @@ def process_type_annotation(self, s: AssignmentStmt) -> None: if isinstance(lvalue.node, Var): lvalue.node.is_abstract_var = True else: - if (any(isinstance(lv, NameExpr) and lv.is_inferred_def for lv in s.lvalues) and - self.type and self.type.is_protocol and not self.is_func_scope()): + if (self.type and self.type.is_protocol and + self.is_annotated_protocol_member(s) and not self.is_func_scope()): self.fail('All protocol members must have explicitly declared types', s) # Set the type if the rvalue is a simple literal (even if the above error occurred). if len(s.lvalues) == 1 and isinstance(s.lvalues[0], RefExpr): @@ -2332,6 +2332,19 @@ def process_type_annotation(self, s: AssignmentStmt) -> None: for lvalue in s.lvalues: self.store_declared_types(lvalue, s.type) + def is_annotated_protocol_member(self, s: AssignmentStmt) -> bool: + """Check whether a protocol member is annotated. + + There are some exceptions that can be left unannotated, like ``__slots__``.""" + return any( + ( + isinstance(lv, NameExpr) + and lv.name != '__slots__' + and lv.is_inferred_def + ) + for lv in s.lvalues + ) + def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Optional[Type]: """Return builtins.int if rvalue is an int literal, etc. diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index b78becc88be49..cdafca134e1a1 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2499,3 +2499,11 @@ reveal_type(abs(3)) # N: Revealed type is 'builtins.int*' reveal_type(abs(ALL)) # N: Revealed type is 'builtins.int*' [builtins fixtures/float.pyi] [typing fixtures/typing-full.pyi] + +[case testProtocolWithSlots] +from typing import Protocol + +class A(Protocol): + __slots__ = () + +[builtins fixtures/tuple.pyi]