Skip to content

Commit 7ce3568

Browse files
authored
Exclude the same special attributes from Protocol as CPython (#15490)
1 parent 9511daa commit 7ce3568

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

mypy/nodes.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2802,6 +2802,25 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:
28022802
return visitor.visit_temp_node(self)
28032803

28042804

2805+
# Special attributes not collected as protocol members by Python 3.12
2806+
# See typing._SPECIAL_NAMES
2807+
EXCLUDED_PROTOCOL_ATTRIBUTES: Final = frozenset(
2808+
{
2809+
"__abstractmethods__",
2810+
"__annotations__",
2811+
"__dict__",
2812+
"__doc__",
2813+
"__init__",
2814+
"__module__",
2815+
"__new__",
2816+
"__slots__",
2817+
"__subclasshook__",
2818+
"__weakref__",
2819+
"__class_getitem__", # Since Python 3.9
2820+
}
2821+
)
2822+
2823+
28052824
class TypeInfo(SymbolNode):
28062825
"""The type structure of a single class.
28072826
@@ -3116,6 +3135,8 @@ def protocol_members(self) -> list[str]:
31163135
if isinstance(node.node, (TypeAlias, TypeVarExpr, MypyFile)):
31173136
# These are auxiliary definitions (and type aliases are prohibited).
31183137
continue
3138+
if name in EXCLUDED_PROTOCOL_ATTRIBUTES:
3139+
continue
31193140
members.add(name)
31203141
return sorted(list(members))
31213142

test-data/unit/check-protocols.test

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2789,6 +2789,70 @@ class A(Protocol):
27892789

27902790
[builtins fixtures/tuple.pyi]
27912791

2792+
[case testProtocolSlotsIsNotProtocolMember]
2793+
# https://github.com/python/mypy/issues/11884
2794+
from typing import Protocol
2795+
2796+
class Foo(Protocol):
2797+
__slots__ = ()
2798+
class NoSlots:
2799+
pass
2800+
class EmptySlots:
2801+
__slots__ = ()
2802+
class TupleSlots:
2803+
__slots__ = ('x', 'y')
2804+
class StringSlots:
2805+
__slots__ = 'x y'
2806+
class InitSlots:
2807+
__slots__ = ('x',)
2808+
def __init__(self) -> None:
2809+
self.x = None
2810+
def foo(f: Foo):
2811+
pass
2812+
2813+
# All should pass:
2814+
foo(NoSlots())
2815+
foo(EmptySlots())
2816+
foo(TupleSlots())
2817+
foo(StringSlots())
2818+
foo(InitSlots())
2819+
[builtins fixtures/tuple.pyi]
2820+
2821+
[case testProtocolSlotsAndRuntimeCheckable]
2822+
from typing import Protocol, runtime_checkable
2823+
2824+
@runtime_checkable
2825+
class Foo(Protocol):
2826+
__slots__ = ()
2827+
class Bar:
2828+
pass
2829+
issubclass(Bar, Foo) # Used to be an error, when `__slots__` counted as a protocol member
2830+
[builtins fixtures/isinstance.pyi]
2831+
[typing fixtures/typing-full.pyi]
2832+
2833+
2834+
[case testProtocolWithClassGetItem]
2835+
# https://github.com/python/mypy/issues/11886
2836+
from typing import Any, Iterable, Protocol, Union
2837+
2838+
class B:
2839+
...
2840+
2841+
class C:
2842+
def __class_getitem__(cls, __item: Any) -> Any:
2843+
...
2844+
2845+
class SupportsClassGetItem(Protocol):
2846+
__slots__: Union[str, Iterable[str]] = ()
2847+
def __class_getitem__(cls, __item: Any) -> Any:
2848+
...
2849+
2850+
b1: SupportsClassGetItem = B()
2851+
c1: SupportsClassGetItem = C()
2852+
[builtins fixtures/tuple.pyi]
2853+
[typing fixtures/typing-full.pyi]
2854+
2855+
27922856
[case testNoneVsProtocol]
27932857
# mypy: strict-optional
27942858
from typing_extensions import Protocol

0 commit comments

Comments
 (0)