@@ -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