Skip to content

Backport type_params fix from CPython #646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
7 changes: 3 additions & 4 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down
18 changes: 4 additions & 14 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading