Skip to content

Commit

Permalink
gh-105280: Ensure isinstance([], collections.abc.Mapping) always ev…
Browse files Browse the repository at this point in the history
…aluates to `False` (#105281)
  • Loading branch information
AlexWaygood committed Jun 5, 2023
1 parent 058b960 commit 08756db
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 8 deletions.
14 changes: 14 additions & 0 deletions Lib/test/test_typing.py
Expand Up @@ -3,6 +3,7 @@
import collections.abc
from collections import defaultdict
from functools import lru_cache, wraps
import gc
import inspect
import itertools
import pickle
Expand Down Expand Up @@ -2758,6 +2759,19 @@ def x(self): ...
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
Expand Down
28 changes: 20 additions & 8 deletions Lib/typing.py
Expand Up @@ -1771,6 +1771,25 @@ def _pickle_pskwargs(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):
Expand Down Expand Up @@ -1906,14 +1925,7 @@ def _proto_hook(other):
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

Expand Down
@@ -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.

0 comments on commit 08756db

Please sign in to comment.