Skip to content

Commit 71c9074

Browse files
committed
Simplify scope logic in ref.evaluate() to ensure double-evaluation works
1 parent fc00d21 commit 71c9074

1 file changed

Lines changed: 14 additions & 18 deletions

File tree

Lib/annotationlib.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -156,37 +156,33 @@ def evaluate(
156156
locals.update(vars(owner))
157157

158158
if type_params is None and owner is not None:
159-
# "Inject" type parameters into the local namespace
160-
# (unless they are shadowed by assignments *in* the local namespace),
161-
# as a way of emulating annotation scopes when calling `eval()`
162159
type_params = getattr(owner, "__type_params__", None)
163160

164-
# Nonlocals logically sit between the locals and the globals. We simulate this
165-
# by overriding the globals.
166-
if isinstance(self.__cell__, dict):
167-
globals = dict(globals)
168-
169-
# Type parameters exist in their own scope, which is logically
170-
# between the locals and the globals.
171-
type_param_scope = {}
161+
# "Inject" type parameters into the local namespace
162+
# (unless they are shadowed by assignments *in* the local namespace),
163+
# as a way of emulating annotation scopes when calling `eval()`
172164
if type_params is not None:
173165
for param in type_params:
174-
type_param_scope[param.__name__] = param
166+
if param.__name__ not in locals:
167+
locals[param.__name__] = param
168+
169+
# Similar logic can be used for nonlocals, which should not
170+
# override locals.
175171
if isinstance(self.__cell__, dict):
176172
for cell_name, cell_value in self.__cell__.items():
177173
try:
178-
globals[cell_name] = cell_value.cell_contents
174+
if cell_name not in locals:
175+
locals[cell_name] = cell_value.cell_contents
179176
except ValueError:
180177
pass
178+
181179
if self.__extra_names__:
182-
locals = {**locals, **self.__extra_names__}
180+
locals.update(self.__extra_names__)
183181

184182
arg = self.__forward_arg__
185183
if arg.isidentifier() and not keyword.iskeyword(arg):
186184
if arg in locals:
187185
return locals[arg]
188-
elif arg in type_param_scope:
189-
return type_param_scope[arg]
190186
elif arg in globals:
191187
return globals[arg]
192188
elif hasattr(builtins, arg):
@@ -198,15 +194,15 @@ def evaluate(
198194
else:
199195
code = self.__forward_code__
200196
try:
201-
return eval(code, globals=globals, locals={**type_param_scope, **locals})
197+
return eval(code, globals=globals, locals=locals)
202198
except Exception:
203199
if not is_forwardref_format:
204200
raise
205201

206202
# All variables, in scoping order, should be checked before
207203
# triggering __missing__ to create a _Stringifier.
208204
new_locals = _StringifierDict(
209-
{**builtins.__dict__, **globals, **type_param_scope, **locals},
205+
{**builtins.__dict__, **globals, **locals},
210206
globals=globals,
211207
owner=owner,
212208
is_class=self.__forward_is_class__,

0 commit comments

Comments
 (0)