Skip to content

Commit e6982c5

Browse files
[3.12] gh-105834: Add tests for calling issubclass() between two protocols (GH-105835) (#105859)
Some parts of the implementation of `typing.Protocol` had poor test coverage (cherry picked from commit 70c075c) Co-authored-by: Alex Waygood <[email protected]>
1 parent 32d8b56 commit e6982c5

File tree

1 file changed

+74
-0
lines changed

1 file changed

+74
-0
lines changed

Lib/test/test_typing.py

+74
Original file line numberDiff line numberDiff line change
@@ -2759,6 +2759,80 @@ def x(self): ...
27592759
with self.assertRaisesRegex(TypeError, only_classes_allowed):
27602760
issubclass(1, BadPG)
27612761

2762+
def test_implicit_issubclass_between_two_protocols(self):
2763+
@runtime_checkable
2764+
class CallableMembersProto(Protocol):
2765+
def meth(self): ...
2766+
2767+
# All the below protocols should be considered "subclasses"
2768+
# of CallableMembersProto at runtime,
2769+
# even though none of them explicitly subclass CallableMembersProto
2770+
2771+
class IdenticalProto(Protocol):
2772+
def meth(self): ...
2773+
2774+
class SupersetProto(Protocol):
2775+
def meth(self): ...
2776+
def meth2(self): ...
2777+
2778+
class NonCallableMembersProto(Protocol):
2779+
meth: Callable[[], None]
2780+
2781+
class NonCallableMembersSupersetProto(Protocol):
2782+
meth: Callable[[], None]
2783+
meth2: Callable[[str, int], bool]
2784+
2785+
class MixedMembersProto1(Protocol):
2786+
meth: Callable[[], None]
2787+
def meth2(self): ...
2788+
2789+
class MixedMembersProto2(Protocol):
2790+
def meth(self): ...
2791+
meth2: Callable[[str, int], bool]
2792+
2793+
for proto in (
2794+
IdenticalProto, SupersetProto, NonCallableMembersProto,
2795+
NonCallableMembersSupersetProto, MixedMembersProto1, MixedMembersProto2
2796+
):
2797+
with self.subTest(proto=proto.__name__):
2798+
self.assertIsSubclass(proto, CallableMembersProto)
2799+
2800+
# These two shouldn't be considered subclasses of CallableMembersProto, however,
2801+
# since they don't have the `meth` protocol member
2802+
2803+
class EmptyProtocol(Protocol): ...
2804+
class UnrelatedProtocol(Protocol):
2805+
def wut(self): ...
2806+
2807+
self.assertNotIsSubclass(EmptyProtocol, CallableMembersProto)
2808+
self.assertNotIsSubclass(UnrelatedProtocol, CallableMembersProto)
2809+
2810+
# These aren't protocols at all (despite having annotations),
2811+
# so they should only be considered subclasses of CallableMembersProto
2812+
# if they *actually have an attribute* matching the `meth` member
2813+
# (just having an annotation is insufficient)
2814+
2815+
class AnnotatedButNotAProtocol:
2816+
meth: Callable[[], None]
2817+
2818+
class NotAProtocolButAnImplicitSubclass:
2819+
def meth(self): pass
2820+
2821+
class NotAProtocolButAnImplicitSubclass2:
2822+
meth: Callable[[], None]
2823+
def meth(self): pass
2824+
2825+
class NotAProtocolButAnImplicitSubclass3:
2826+
meth: Callable[[], None]
2827+
meth2: Callable[[int, str], bool]
2828+
def meth(self): pass
2829+
def meth(self, x, y): return True
2830+
2831+
self.assertNotIsSubclass(AnnotatedButNotAProtocol, CallableMembersProto)
2832+
self.assertIsSubclass(NotAProtocolButAnImplicitSubclass, CallableMembersProto)
2833+
self.assertIsSubclass(NotAProtocolButAnImplicitSubclass2, CallableMembersProto)
2834+
self.assertIsSubclass(NotAProtocolButAnImplicitSubclass3, CallableMembersProto)
2835+
27622836
def test_isinstance_checks_not_at_whim_of_gc(self):
27632837
self.addCleanup(gc.enable)
27642838
gc.disable()

0 commit comments

Comments
 (0)