@@ -1987,6 +1987,80 @@ def x(self): ...
19871987 with self .assertRaisesRegex (TypeError , only_classes_allowed ):
19881988 issubclass (1 , BadPG )
19891989
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+
19902064 @skip_if_py312b1
19912065 def test_issubclass_and_isinstance_on_Protocol_itself (self ):
19922066 class C :
0 commit comments