]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-105280: Ensure `isinstance([], collections.abc.Mapping)` always evaluates...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 5 Jun 2023 14:51:26 +0000 (07:51 -0700)
committerGitHub <noreply@github.com>
Mon, 5 Jun 2023 14:51:26 +0000 (14:51 +0000)
gh-105280: Ensure `isinstance([], collections.abc.Mapping)` always evaluates to `False` (GH-105281)
(cherry picked from commit 08756dbba647440803d2ba4545ba0ab2f0cdfe1c)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2023-06-04-12-16-47.gh-issue-105280.srRbCe.rst [new file with mode: 0644]

index 3a2f7393eb46c50ad647865d6e302c31e8ace4a7..ef1db06109eca3a1ac69694fe0747613f3369d93 100644 (file)
@@ -3,6 +3,7 @@ import collections
 import collections.abc
 from collections import defaultdict
 from functools import lru_cache, wraps
+import gc
 import inspect
 import itertools
 import pickle
@@ -2758,6 +2759,19 @@ class ProtocolTests(BaseTestCase):
         with self.assertRaisesRegex(TypeError, only_classes_allowed):
             issubclass(1, BadPG)
 
+    def test_isinstance_checks_not_at_whim_of_gc(self):
+        self.addCleanup(gc.enable)
+        gc.disable()
+
+        with self.assertRaisesRegex(
+            TypeError,
+            "Protocols can only inherit from other protocols"
+        ):
+            class Foo(collections.abc.Mapping, Protocol):
+                pass
+
+        self.assertNotIsInstance([], collections.abc.Mapping)
+
     def test_issubclass_and_isinstance_on_Protocol_itself(self):
         class C:
             def x(self): pass
index 06d50306c4754e9a82723267a21cb073f6d06415..2acb9f052f2af5c1eaf690bf7f4a06c3bc4fec58 100644 (file)
@@ -1777,6 +1777,25 @@ del _pickle_psargs, _pickle_pskwargs
 class _ProtocolMeta(ABCMeta):
     # This metaclass is somewhat unfortunate,
     # but is necessary for several reasons...
+    def __new__(mcls, name, bases, namespace, /, **kwargs):
+        if name == "Protocol" and bases == (Generic,):
+            pass
+        elif Protocol in bases:
+            for base in bases:
+                if not (
+                    base in {object, Generic}
+                    or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
+                    or (
+                        issubclass(base, Generic)
+                        and getattr(base, "_is_protocol", False)
+                    )
+                ):
+                    raise TypeError(
+                        f"Protocols can only inherit from other protocols, "
+                        f"got {base!r}"
+                    )
+        return super().__new__(mcls, name, bases, namespace, **kwargs)
+
     def __init__(cls, *args, **kwargs):
         super().__init__(*args, **kwargs)
         if getattr(cls, "_is_protocol", False):
@@ -1912,14 +1931,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
         if not cls._is_protocol:
             return
 
-        # ... otherwise check consistency of bases, and prohibit instantiation.
-        for base in cls.__bases__:
-            if not (base in (object, Generic) or
-                    base.__module__ in _PROTO_ALLOWLIST and
-                    base.__name__ in _PROTO_ALLOWLIST[base.__module__] or
-                    issubclass(base, Generic) and getattr(base, '_is_protocol', False)):
-                raise TypeError('Protocols can only inherit from other'
-                                ' protocols, got %r' % base)
+        # ... otherwise prohibit instantiation.
         if cls.__init__ is Protocol.__init__:
             cls.__init__ = _no_init_or_replace_init
 
diff --git a/Misc/NEWS.d/next/Library/2023-06-04-12-16-47.gh-issue-105280.srRbCe.rst b/Misc/NEWS.d/next/Library/2023-06-04-12-16-47.gh-issue-105280.srRbCe.rst
new file mode 100644 (file)
index 0000000..8e46964
--- /dev/null
@@ -0,0 +1,4 @@
+Fix bug where ``isinstance([], collections.abc.Mapping)`` could evaluate to
+``True`` if garbage collection happened at the wrong time. The bug was
+caused by changes to the implementation of :class:`typing.Protocol` in
+Python 3.12.