From b2ad56f8e06d73bd58cb2d9292992c5a905e40da Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2024 03:20:51 -0700 Subject: [PATCH 1/6] Avoid error if origin has a buggy __eq__ #419 --- src/typing_extensions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 46084fa5..74f8decb 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2958,9 +2958,10 @@ def _has_generic_or_protocol_as_origin() -> bool: except AttributeError: return False # err on the side of leniency else: - return frame.f_locals.get("origin") in ( - typing.Generic, Protocol, typing.Protocol - ) + origin = frame.f_locals.get("origin") + # Cannot use "in" because origin may be an object with a buggy __eq__ that + # throws an error. + return origin is typing.Generic or origin is Protocol or origin is typing.Protocol _TYPEVARTUPLE_TYPES = {TypeVarTuple, getattr(typing, "TypeVarTuple", None)} From 68b5fd2ca4dc2dc9b314aa4c1832455f45518613 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2024 03:59:16 -0700 Subject: [PATCH 2/6] Tests etc. --- CHANGELOG.md | 6 ++++++ src/test_typing_extensions.py | 15 +++++++++++++++ src/typing_extensions.py | 3 +++ 3 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a5937a6..776a101e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Unreleased + +- Fix regression in v4.12.0 where specialization of certain + generics with an overridden `__eq__` method would raise errors. + Patch by Jelle Zijlstra. + # Release 4.12.1 (June 1, 2024) - Preliminary changes for compatibility with the draft implementation diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 8ba0bf74..22c478ad 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -6617,6 +6617,21 @@ def test_allow_default_after_non_default_in_alias(self): a4 = Callable[[Unpack[Ts]], T] self.assertEqual(a4.__args__, (Unpack[Ts], T)) + def test_generic_with_broken_eq(self): + # See https://github.com/python/typing_extensions/pull/422 for context + class BrokenEq(abc.ABCMeta): + def __eq__(self, other): + if other is typing_extensions.Protocol: + raise TypeError("I'm broken") + return False + + class G(Generic[T], metaclass=BrokenEq): + pass + + alias = G[int] + self.assertIs(get_origin(alias), G) + self.assertEqual(get_args(alias), (int,)) + @skipIf( sys.version_info < (3, 11, 1), "Not yet backported for older versions of Python" diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 74f8decb..619bcdea 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2958,6 +2958,9 @@ def _has_generic_or_protocol_as_origin() -> bool: except AttributeError: return False # err on the side of leniency else: + # If we somehow get invoked from outside typing.py, also err on the side of leniency + if frame.f_globals.get("__name__") != "typing": + return False origin = frame.f_locals.get("origin") # Cannot use "in" because origin may be an object with a buggy __eq__ that # throws an error. From ea81ec06c007c180eebfbe5863fd93d4f047364f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2024 04:00:37 -0700 Subject: [PATCH 3/6] lint --- src/typing_extensions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 619bcdea..8bb155dd 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2958,7 +2958,8 @@ def _has_generic_or_protocol_as_origin() -> bool: except AttributeError: return False # err on the side of leniency else: - # If we somehow get invoked from outside typing.py, also err on the side of leniency + # If we somehow get invoked from outside typing.py, + # also err on the side of leniency if frame.f_globals.get("__name__") != "typing": return False origin = frame.f_locals.get("origin") From afa62ef4eda708c3c2ea286c6ce5031481f10d82 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2024 04:02:26 -0700 Subject: [PATCH 4/6] Update src/test_typing_extensions.py Co-authored-by: Alex Waygood --- src/test_typing_extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 22c478ad..2fea13ec 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -6617,6 +6617,7 @@ def test_allow_default_after_non_default_in_alias(self): a4 = Callable[[Unpack[Ts]], T] self.assertEqual(a4.__args__, (Unpack[Ts], T)) + @skip_if_py313_beta_1 def test_generic_with_broken_eq(self): # See https://github.com/python/typing_extensions/pull/422 for context class BrokenEq(abc.ABCMeta): From 5ae6cc03e7f33caa20d26080141cdeedd50300af Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2024 04:31:26 -0700 Subject: [PATCH 5/6] Catch ValueError too --- src/typing_extensions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 8bb155dd..dec429ca 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2954,8 +2954,10 @@ def _check_generic(cls, parameters, elen): def _has_generic_or_protocol_as_origin() -> bool: try: frame = sys._getframe(2) - # not all platforms have sys._getframe() - except AttributeError: + # - Catch AttributeError: not all Python implementations have sys._getframe() + # - Catch ValueError: maybe we're called from an unexpected module + # and the call stack isn't deep enough + except (AttributeError, ValueError): return False # err on the side of leniency else: # If we somehow get invoked from outside typing.py, From 0469f25fb48315f28782f9902d04b3a2c8ebc740 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2024 04:42:05 -0700 Subject: [PATCH 6/6] Update src/test_typing_extensions.py Co-authored-by: Alex Waygood --- src/test_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 2fea13ec..bf7600a1 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -6620,7 +6620,7 @@ def test_allow_default_after_non_default_in_alias(self): @skip_if_py313_beta_1 def test_generic_with_broken_eq(self): # See https://github.com/python/typing_extensions/pull/422 for context - class BrokenEq(abc.ABCMeta): + class BrokenEq(type): def __eq__(self, other): if other is typing_extensions.Protocol: raise TypeError("I'm broken")