@@ -2759,6 +2759,80 @@ def x(self): ...
2759
2759
with self .assertRaisesRegex (TypeError , only_classes_allowed ):
2760
2760
issubclass (1 , BadPG )
2761
2761
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
+
2762
2836
def test_isinstance_checks_not_at_whim_of_gc (self ):
2763
2837
self .addCleanup (gc .enable )
2764
2838
gc .disable ()
0 commit comments