diff --git a/CHANGELOG.md b/CHANGELOG.md index cb143299..cc2122f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ [`annotationlib.type_repr`](https://docs.python.org/3.14/library/annotationlib.html#annotationlib.type_repr), introduced in Python 3.14 (CPython PR [#124551](https://github.com/python/cpython/pull/124551), originally by Jelle Zijlstra). Patch by Semyon Moroz. - +- Fix behavior of type params in `typing_extensions.evaluate_forward_ref`. Backport of + CPython PR [#137227](https://github.com/python/cpython/pull/137227) by Jelle Zijlstra. # Release 4.14.1 (July 4, 2025) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 7a6380a3..16370bc0 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -9194,10 +9194,9 @@ class Gen[Tx]: not_Tx = TypeVar("Tx") # different TypeVar with same name self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx"), type_params=(not_Tx,), owner=Gen), not_Tx) - # globals can take higher precedence - if _FORWARD_REF_HAS_CLASS: - self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx", is_class=True), owner=Gen, globals={"Tx": str}), str) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx", is_class=True), owner=Gen, type_params=(not_Tx,), globals={"Tx": str}), str) + # globals do not take higher precedence + self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx", is_class=True), owner=Gen, globals={"Tx": str}), Tx) + self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx", is_class=True), owner=Gen, type_params=(not_Tx,), globals={"Tx": str}), not_Tx) with self.assertRaises(NameError): evaluate_forward_ref(typing.ForwardRef("alias"), type_params=Gen.__type_params__) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 84cf383f..bd424da9 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -4065,23 +4065,13 @@ def _eval_with_owner( # as a way of emulating annotation scopes when calling `eval()` type_params = getattr(owner, "__type_params__", None) - # type parameters require some special handling, - # as they exist in their own scope - # but `eval()` does not have a dedicated parameter for that scope. - # For classes, names in type parameter scopes should override - # names in the global scope (which here are called `localns`!), - # but should in turn be overridden by names in the class scope - # (which here are called `globalns`!) + # Type parameters exist in their own scope, which is logically + # between the locals and the globals. We simulate this by adding + # them to the globals. if type_params is not None: globals = dict(globals) - locals = dict(locals) for param in type_params: - param_name = param.__name__ - if ( - _FORWARD_REF_HAS_CLASS and not forward_ref.__forward_is_class__ - ) or param_name not in globals: - globals[param_name] = param - locals.pop(param_name, None) + globals[param.__name__] = param arg = forward_ref.__forward_arg__ if arg.isidentifier() and not keyword.iskeyword(arg):