@@ -1987,6 +1987,80 @@ def x(self): ...
1987
1987
with self .assertRaisesRegex (TypeError , only_classes_allowed ):
1988
1988
issubclass (1 , BadPG )
1989
1989
1990
+ def test_implicit_issubclass_between_two_protocols (self ):
1991
+ @runtime_checkable
1992
+ class CallableMembersProto (Protocol ):
1993
+ def meth (self ): ...
1994
+
1995
+ # All the below protocols should be considered "subclasses"
1996
+ # of CallableMembersProto at runtime,
1997
+ # even though none of them explicitly subclass CallableMembersProto
1998
+
1999
+ class IdenticalProto (Protocol ):
2000
+ def meth (self ): ...
2001
+
2002
+ class SupersetProto (Protocol ):
2003
+ def meth (self ): ...
2004
+ def meth2 (self ): ...
2005
+
2006
+ class NonCallableMembersProto (Protocol ):
2007
+ meth : Callable [[], None ]
2008
+
2009
+ class NonCallableMembersSupersetProto (Protocol ):
2010
+ meth : Callable [[], None ]
2011
+ meth2 : Callable [[str , int ], bool ]
2012
+
2013
+ class MixedMembersProto1 (Protocol ):
2014
+ meth : Callable [[], None ]
2015
+ def meth2 (self ): ...
2016
+
2017
+ class MixedMembersProto2 (Protocol ):
2018
+ def meth (self ): ...
2019
+ meth2 : Callable [[str , int ], bool ]
2020
+
2021
+ for proto in (
2022
+ IdenticalProto , SupersetProto , NonCallableMembersProto ,
2023
+ NonCallableMembersSupersetProto , MixedMembersProto1 , MixedMembersProto2
2024
+ ):
2025
+ with self .subTest (proto = proto .__name__ ):
2026
+ self .assertIsSubclass (proto , CallableMembersProto )
2027
+
2028
+ # These two shouldn't be considered subclasses of CallableMembersProto, however,
2029
+ # since they don't have the `meth` protocol member
2030
+
2031
+ class EmptyProtocol (Protocol ): ...
2032
+ class UnrelatedProtocol (Protocol ):
2033
+ def wut (self ): ...
2034
+
2035
+ self .assertNotIsSubclass (EmptyProtocol , CallableMembersProto )
2036
+ self .assertNotIsSubclass (UnrelatedProtocol , CallableMembersProto )
2037
+
2038
+ # These aren't protocols at all (despite having annotations),
2039
+ # so they should only be considered subclasses of CallableMembersProto
2040
+ # if they *actually have an attribute* matching the `meth` member
2041
+ # (just having an annotation is insufficient)
2042
+
2043
+ class AnnotatedButNotAProtocol :
2044
+ meth : Callable [[], None ]
2045
+
2046
+ class NotAProtocolButAnImplicitSubclass :
2047
+ def meth (self ): pass
2048
+
2049
+ class NotAProtocolButAnImplicitSubclass2 :
2050
+ meth : Callable [[], None ]
2051
+ def meth (self ): pass
2052
+
2053
+ class NotAProtocolButAnImplicitSubclass3 :
2054
+ meth : Callable [[], None ]
2055
+ meth2 : Callable [[int , str ], bool ]
2056
+ def meth (self ): pass
2057
+ def meth (self , x , y ): return True
2058
+
2059
+ self .assertNotIsSubclass (AnnotatedButNotAProtocol , CallableMembersProto )
2060
+ self .assertIsSubclass (NotAProtocolButAnImplicitSubclass , CallableMembersProto )
2061
+ self .assertIsSubclass (NotAProtocolButAnImplicitSubclass2 , CallableMembersProto )
2062
+ self .assertIsSubclass (NotAProtocolButAnImplicitSubclass3 , CallableMembersProto )
2063
+
1990
2064
@skip_if_py312b1
1991
2065
def test_issubclass_and_isinstance_on_Protocol_itself (self ):
1992
2066
class C :
0 commit comments