]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-105237: Allow calling `issubclass(X, typing.Protocol)` again (GH-105239...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 5 Jun 2023 14:06:25 +0000 (07:06 -0700)
committerGitHub <noreply@github.com>
Mon, 5 Jun 2023 14:06:25 +0000 (14:06 +0000)
gh-105237: Allow calling `issubclass(X, typing.Protocol)` again (GH-105239)
(cherry picked from commit cdfb201bfa35b7c50de5099c6d9078c806851d98)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2023-06-02-14-57-11.gh-issue-105239.SAmuuj.rst [new file with mode: 0644]

index 5480a981ad56478f565f3dc6afa91a35da503508..3a2f7393eb46c50ad647865d6e302c31e8ace4a7 100644 (file)
@@ -2758,6 +2758,65 @@ class ProtocolTests(BaseTestCase):
         with self.assertRaisesRegex(TypeError, only_classes_allowed):
             issubclass(1, BadPG)
 
+    def test_issubclass_and_isinstance_on_Protocol_itself(self):
+        class C:
+            def x(self): pass
+
+        self.assertNotIsSubclass(object, Protocol)
+        self.assertNotIsInstance(object(), Protocol)
+
+        self.assertNotIsSubclass(str, Protocol)
+        self.assertNotIsInstance('foo', Protocol)
+
+        self.assertNotIsSubclass(C, Protocol)
+        self.assertNotIsInstance(C(), Protocol)
+
+        only_classes_allowed = r"issubclass\(\) arg 1 must be a class"
+
+        with self.assertRaisesRegex(TypeError, only_classes_allowed):
+            issubclass(1, Protocol)
+        with self.assertRaisesRegex(TypeError, only_classes_allowed):
+            issubclass('foo', Protocol)
+        with self.assertRaisesRegex(TypeError, only_classes_allowed):
+            issubclass(C(), Protocol)
+
+        T = TypeVar('T')
+
+        @runtime_checkable
+        class EmptyProtocol(Protocol): pass
+
+        @runtime_checkable
+        class SupportsStartsWith(Protocol):
+            def startswith(self, x: str) -> bool: ...
+
+        @runtime_checkable
+        class SupportsX(Protocol[T]):
+            def x(self): ...
+
+        for proto in EmptyProtocol, SupportsStartsWith, SupportsX:
+            with self.subTest(proto=proto.__name__):
+                self.assertIsSubclass(proto, Protocol)
+
+        # gh-105237 / PR #105239:
+        # check that the presence of Protocol subclasses
+        # where `issubclass(X, <subclass>)` evaluates to True
+        # doesn't influence the result of `issubclass(X, Protocol)`
+
+        self.assertIsSubclass(object, EmptyProtocol)
+        self.assertIsInstance(object(), EmptyProtocol)
+        self.assertNotIsSubclass(object, Protocol)
+        self.assertNotIsInstance(object(), Protocol)
+
+        self.assertIsSubclass(str, SupportsStartsWith)
+        self.assertIsInstance('foo', SupportsStartsWith)
+        self.assertNotIsSubclass(str, Protocol)
+        self.assertNotIsInstance('foo', Protocol)
+
+        self.assertIsSubclass(C, SupportsX)
+        self.assertIsInstance(C(), SupportsX)
+        self.assertNotIsSubclass(C, Protocol)
+        self.assertNotIsInstance(C(), Protocol)
+
     def test_protocols_issubclass_non_callable(self):
         class C:
             x = 1
index 2383d807ec58d16d54bc8921f4345a836aea8922..06d50306c4754e9a82723267a21cb073f6d06415 100644 (file)
@@ -1788,6 +1788,8 @@ class _ProtocolMeta(ABCMeta):
             )
 
     def __subclasscheck__(cls, other):
+        if cls is Protocol:
+            return type.__subclasscheck__(cls, other)
         if not isinstance(other, type):
             # Same error message as for issubclass(1, int).
             raise TypeError('issubclass() arg 1 must be a class')
@@ -1809,6 +1811,8 @@ class _ProtocolMeta(ABCMeta):
     def __instancecheck__(cls, instance):
         # We need this method for situations where attributes are
         # assigned in __init__.
+        if cls is Protocol:
+            return type.__instancecheck__(cls, instance)
         if not getattr(cls, "_is_protocol", False):
             # i.e., it's a concrete subclass of a protocol
             return super().__instancecheck__(instance)
diff --git a/Misc/NEWS.d/next/Library/2023-06-02-14-57-11.gh-issue-105239.SAmuuj.rst b/Misc/NEWS.d/next/Library/2023-06-02-14-57-11.gh-issue-105239.SAmuuj.rst
new file mode 100644 (file)
index 0000000..35e1b1a
--- /dev/null
@@ -0,0 +1,2 @@
+Fix longstanding bug where ``issubclass(object, typing.Protocol)`` would
+evaluate to ``True`` in some edge cases. Patch by Alex Waygood.