mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
[3.14] gh-137969: Fix evaluation of ref.evaluate(format=Format.FORWARDREF) objects (GH-138075) (#140929)
gh-137969: Fix evaluation of `ref.evaluate(format=Format.FORWARDREF)` objects (GH-138075)
(cherry picked from commit 63e01d6bae)
Co-authored-by: dr-carlos <77367421+dr-carlos@users.noreply.github.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
parent
23e3771045
commit
cdb6fe89ae
3 changed files with 19 additions and 6 deletions
|
|
@ -159,12 +159,12 @@ def evaluate(
|
||||||
type_params = getattr(owner, "__type_params__", None)
|
type_params = getattr(owner, "__type_params__", None)
|
||||||
|
|
||||||
# Type parameters exist in their own scope, which is logically
|
# Type parameters exist in their own scope, which is logically
|
||||||
# between the locals and the globals. We simulate this by adding
|
# between the locals and the globals.
|
||||||
# them to the globals.
|
type_param_scope = {}
|
||||||
if type_params is not None:
|
if type_params is not None:
|
||||||
globals = dict(globals)
|
|
||||||
for param in type_params:
|
for param in type_params:
|
||||||
globals[param.__name__] = param
|
type_param_scope[param.__name__] = param
|
||||||
|
|
||||||
if self.__extra_names__:
|
if self.__extra_names__:
|
||||||
locals = {**locals, **self.__extra_names__}
|
locals = {**locals, **self.__extra_names__}
|
||||||
|
|
||||||
|
|
@ -172,6 +172,8 @@ def evaluate(
|
||||||
if arg.isidentifier() and not keyword.iskeyword(arg):
|
if arg.isidentifier() and not keyword.iskeyword(arg):
|
||||||
if arg in locals:
|
if arg in locals:
|
||||||
return locals[arg]
|
return locals[arg]
|
||||||
|
elif arg in type_param_scope:
|
||||||
|
return type_param_scope[arg]
|
||||||
elif arg in globals:
|
elif arg in globals:
|
||||||
return globals[arg]
|
return globals[arg]
|
||||||
elif hasattr(builtins, arg):
|
elif hasattr(builtins, arg):
|
||||||
|
|
@ -183,7 +185,7 @@ def evaluate(
|
||||||
else:
|
else:
|
||||||
code = self.__forward_code__
|
code = self.__forward_code__
|
||||||
try:
|
try:
|
||||||
return eval(code, globals=globals, locals=locals)
|
return eval(code, globals=globals, locals={**type_param_scope, **locals})
|
||||||
except Exception:
|
except Exception:
|
||||||
if not is_forwardref_format:
|
if not is_forwardref_format:
|
||||||
raise
|
raise
|
||||||
|
|
@ -191,7 +193,7 @@ def evaluate(
|
||||||
# All variables, in scoping order, should be checked before
|
# All variables, in scoping order, should be checked before
|
||||||
# triggering __missing__ to create a _Stringifier.
|
# triggering __missing__ to create a _Stringifier.
|
||||||
new_locals = _StringifierDict(
|
new_locals = _StringifierDict(
|
||||||
{**builtins.__dict__, **globals, **locals},
|
{**builtins.__dict__, **globals, **type_param_scope, **locals},
|
||||||
globals=globals,
|
globals=globals,
|
||||||
owner=owner,
|
owner=owner,
|
||||||
is_class=self.__forward_is_class__,
|
is_class=self.__forward_is_class__,
|
||||||
|
|
|
||||||
|
|
@ -1911,6 +1911,15 @@ def test_fwdref_invalid_syntax(self):
|
||||||
with self.assertRaises(SyntaxError):
|
with self.assertRaises(SyntaxError):
|
||||||
fr.evaluate()
|
fr.evaluate()
|
||||||
|
|
||||||
|
def test_re_evaluate_generics(self):
|
||||||
|
global alias
|
||||||
|
class C:
|
||||||
|
x: alias[int]
|
||||||
|
|
||||||
|
evaluated = get_annotations(C, format=Format.FORWARDREF)["x"].evaluate(format=Format.FORWARDREF)
|
||||||
|
alias = list
|
||||||
|
self.assertEqual(evaluated.evaluate(), list[int])
|
||||||
|
|
||||||
|
|
||||||
class TestAnnotationLib(unittest.TestCase):
|
class TestAnnotationLib(unittest.TestCase):
|
||||||
def test__all__(self):
|
def test__all__(self):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix :meth:`annotationlib.ForwardRef.evaluate` returning :class:`annotationlib.ForwardRef`
|
||||||
|
objects which do not update in new contexts.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue