From 9cb50cbb551be3e511968994775889cb71549ae0 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 1 Jul 2023 11:51:50 +0100 Subject: [PATCH 1/2] Restore compatibility with PyPy <3.9 --- .github/workflows/ci.yml | 2 ++ CHANGELOG.md | 5 +++++ src/typing_extensions.py | 28 +++++++++++++++++++++++++--- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32d12981..78610e27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,8 @@ jobs: - "3.11" - "3.11.0" - "3.12" + - "pypy3.7" + - "pypy3.8" - "pypy3.9" - "pypy3.10" diff --git a/CHANGELOG.md b/CHANGELOG.md index 262643ed..428f304b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Release 4.7.1 (???) + +- Fix `TypedDict`, `NamedTuple` and `is_protocol` tests on PyPy-3.7 and + PyPy-3.8. Patch by Alex Waygood. + # Release 4.7.0 (June 28, 2023) - This is expected to be the last feature release supporting Python 3.7, diff --git a/src/typing_extensions.py b/src/typing_extensions.py index b77c1fdb..4e1f8e07 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -128,6 +128,8 @@ 'no_type_check_decorator', ] +_OLD_PYPY_VERSION = sys.implementation.name == "pypy" and sys.version_info < (3, 9) + # for backward compatibility PEP_560 = True GenericMeta = type @@ -1143,7 +1145,18 @@ class Point2D(TypedDict): return td _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {}) - TypedDict.__mro_entries__ = lambda bases: (_TypedDict,) + + if _OLD_PYPY_VERSION: + class _TypedDictType: + __call__ = staticmethod(TypedDict) + + def __mro_entries__(self, bases): + return (_TypedDict,) + + TypedDict = _TypedDictType() + functools.update_wrapper(TypedDict, TypedDict.__call__) + else: + TypedDict.__mro_entries__ = lambda bases: (_TypedDict,) if hasattr(typing, "_TypedDictMeta"): _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) @@ -2710,7 +2723,15 @@ def _namedtuple_mro_entries(bases): assert NamedTuple in bases return (_NamedTuple,) - NamedTuple.__mro_entries__ = _namedtuple_mro_entries + if _OLD_PYPY_VERSION: + class _NamedTupleType: + __call__ = staticmethod(NamedTuple) + __mro_entries__ = staticmethod(_namedtuple_mro_entries) + + NamedTuple = _NamedTupleType() + functools.update_wrapper(NamedTuple, NamedTuple.__call__) + else: + NamedTuple.__mro_entries__ = _namedtuple_mro_entries if hasattr(collections.abc, "Buffer"): @@ -2986,7 +3007,8 @@ def is_protocol(__tp: type) -> bool: return ( isinstance(__tp, type) and getattr(__tp, '_is_protocol', False) - and __tp != Protocol + and __tp is not Protocol + and __tp is not getattr(typing, "Protocol", object()) ) def get_protocol_members(__tp: type) -> typing.FrozenSet[str]: From 826e68d774124ff8be8fd54164abca0ef8d8effc Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 1 Jul 2023 16:31:41 +0100 Subject: [PATCH 2/2] More DRY --- src/typing_extensions.py | 63 +++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 4e1f8e07..901f3b96 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -128,8 +128,6 @@ 'no_type_check_decorator', ] -_OLD_PYPY_VERSION = sys.implementation.name == "pypy" and sys.version_info < (3, 9) - # for backward compatibility PEP_560 = True GenericMeta = type @@ -955,6 +953,21 @@ def __round__(self, ndigits: int = 0) -> T_co: pass +def _ensure_subclassable(mro_entries): + def inner(func): + if sys.implementation.name == "pypy" and sys.version_info < (3, 9): + cls_dict = { + "__call__": staticmethod(func), + "__mro_entries__": staticmethod(mro_entries) + } + t = type(func.__name__, (), cls_dict) + return functools.update_wrapper(t(), func) + else: + func.__mro_entries__ = mro_entries + return func + return inner + + if sys.version_info >= (3, 13): # The standard library TypedDict in Python 3.8 does not store runtime information # about which (if any) keys are optional. See https://bugs.python.org/issue38834 @@ -1061,6 +1074,9 @@ def __subclasscheck__(cls, other): __instancecheck__ = __subclasscheck__ + _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {}) + + @_ensure_subclassable(lambda bases: (_TypedDict,)) def TypedDict(__typename, __fields=_marker, *, total=True, **kwargs): """A simple typed namespace. At runtime it is equivalent to a plain dict. @@ -1144,20 +1160,6 @@ class Point2D(TypedDict): td.__orig_bases__ = (TypedDict,) return td - _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {}) - - if _OLD_PYPY_VERSION: - class _TypedDictType: - __call__ = staticmethod(TypedDict) - - def __mro_entries__(self, bases): - return (_TypedDict,) - - TypedDict = _TypedDictType() - functools.update_wrapper(TypedDict, TypedDict.__call__) - else: - TypedDict.__mro_entries__ = lambda bases: (_TypedDict,) - if hasattr(typing, "_TypedDictMeta"): _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) else: @@ -2646,6 +2648,13 @@ def __new__(cls, typename, bases, ns): nm_tpl.__init_subclass__() return nm_tpl + _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {}) + + def _namedtuple_mro_entries(bases): + assert NamedTuple in bases + return (_NamedTuple,) + + @_ensure_subclassable(_namedtuple_mro_entries) def NamedTuple(__typename, __fields=_marker, **kwargs): """Typed version of namedtuple. @@ -2711,27 +2720,15 @@ class Employee(NamedTuple): nt.__orig_bases__ = (NamedTuple,) return nt - _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {}) - # On 3.8+, alter the signature so that it matches typing.NamedTuple. # The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7, # so just leave the signature as it is on 3.7. if sys.version_info >= (3, 8): - NamedTuple.__text_signature__ = '(typename, fields=None, /, **kwargs)' - - def _namedtuple_mro_entries(bases): - assert NamedTuple in bases - return (_NamedTuple,) - - if _OLD_PYPY_VERSION: - class _NamedTupleType: - __call__ = staticmethod(NamedTuple) - __mro_entries__ = staticmethod(_namedtuple_mro_entries) - - NamedTuple = _NamedTupleType() - functools.update_wrapper(NamedTuple, NamedTuple.__call__) - else: - NamedTuple.__mro_entries__ = _namedtuple_mro_entries + _new_signature = '(typename, fields=None, /, **kwargs)' + if isinstance(NamedTuple, _types.FunctionType): + NamedTuple.__text_signature__ = _new_signature + else: + NamedTuple.__call__.__text_signature__ = _new_signature if hasattr(collections.abc, "Buffer"):