From 7ce3568d823c52e734983333271d315b637a61e6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 26 Jun 2023 13:12:43 -0700 Subject: [PATCH] Exclude the same special attributes from Protocol as CPython (#15490) --- mypy/nodes.py | 21 ++++++++++ test-data/unit/check-protocols.test | 64 +++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/mypy/nodes.py b/mypy/nodes.py index 52dd9948e0c1..212ddb5def37 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2802,6 +2802,25 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_temp_node(self) +# Special attributes not collected as protocol members by Python 3.12 +# See typing._SPECIAL_NAMES +EXCLUDED_PROTOCOL_ATTRIBUTES: Final = frozenset( + { + "__abstractmethods__", + "__annotations__", + "__dict__", + "__doc__", + "__init__", + "__module__", + "__new__", + "__slots__", + "__subclasshook__", + "__weakref__", + "__class_getitem__", # Since Python 3.9 + } +) + + class TypeInfo(SymbolNode): """The type structure of a single class. @@ -3116,6 +3135,8 @@ def protocol_members(self) -> list[str]: if isinstance(node.node, (TypeAlias, TypeVarExpr, MypyFile)): # These are auxiliary definitions (and type aliases are prohibited). continue + if name in EXCLUDED_PROTOCOL_ATTRIBUTES: + continue members.add(name) return sorted(list(members)) diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 6976b8ee0a39..6ba1fde4d022 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2789,6 +2789,70 @@ class A(Protocol): [builtins fixtures/tuple.pyi] +[case testProtocolSlotsIsNotProtocolMember] +# https://github.com/python/mypy/issues/11884 +from typing import Protocol + +class Foo(Protocol): + __slots__ = () +class NoSlots: + pass +class EmptySlots: + __slots__ = () +class TupleSlots: + __slots__ = ('x', 'y') +class StringSlots: + __slots__ = 'x y' +class InitSlots: + __slots__ = ('x',) + def __init__(self) -> None: + self.x = None +def foo(f: Foo): + pass + +# All should pass: +foo(NoSlots()) +foo(EmptySlots()) +foo(TupleSlots()) +foo(StringSlots()) +foo(InitSlots()) +[builtins fixtures/tuple.pyi] + +[case testProtocolSlotsAndRuntimeCheckable] +from typing import Protocol, runtime_checkable + +@runtime_checkable +class Foo(Protocol): + __slots__ = () +class Bar: + pass +issubclass(Bar, Foo) # Used to be an error, when `__slots__` counted as a protocol member +[builtins fixtures/isinstance.pyi] +[typing fixtures/typing-full.pyi] + + +[case testProtocolWithClassGetItem] +# https://github.com/python/mypy/issues/11886 +from typing import Any, Iterable, Protocol, Union + +class B: + ... + +class C: + def __class_getitem__(cls, __item: Any) -> Any: + ... + +class SupportsClassGetItem(Protocol): + __slots__: Union[str, Iterable[str]] = () + def __class_getitem__(cls, __item: Any) -> Any: + ... + +b1: SupportsClassGetItem = B() +c1: SupportsClassGetItem = C() +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] + + [case testNoneVsProtocol] # mypy: strict-optional from typing_extensions import Protocol