mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-138151: Fix annotationlib handling of multiple nonlocals (#138164)
This commit is contained in:
parent
d590685297
commit
b1027d4762
3 changed files with 45 additions and 13 deletions
|
|
@ -85,6 +85,9 @@ def __init__(
|
|||
# These are always set to None here but may be non-None if a ForwardRef
|
||||
# is created through __class__ assignment on a _Stringifier object.
|
||||
self.__globals__ = None
|
||||
# This may be either a cell object (for a ForwardRef referring to a single name)
|
||||
# or a dict mapping cell names to cell objects (for a ForwardRef containing references
|
||||
# to multiple names).
|
||||
self.__cell__ = None
|
||||
self.__extra_names__ = None
|
||||
# These are initially None but serve as a cache and may be set to a non-None
|
||||
|
|
@ -117,7 +120,7 @@ def evaluate(
|
|||
is_forwardref_format = True
|
||||
case _:
|
||||
raise NotImplementedError(format)
|
||||
if self.__cell__ is not None:
|
||||
if isinstance(self.__cell__, types.CellType):
|
||||
try:
|
||||
return self.__cell__.cell_contents
|
||||
except ValueError:
|
||||
|
|
@ -160,11 +163,18 @@ def evaluate(
|
|||
|
||||
# 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:
|
||||
# them to the globals. Similar reasoning applies to nonlocals stored in cells.
|
||||
if type_params is not None or isinstance(self.__cell__, dict):
|
||||
globals = dict(globals)
|
||||
if type_params is not None:
|
||||
for param in type_params:
|
||||
globals[param.__name__] = param
|
||||
if isinstance(self.__cell__, dict):
|
||||
for cell_name, cell_value in self.__cell__.items():
|
||||
try:
|
||||
globals[cell_name] = cell_value.cell_contents
|
||||
except ValueError:
|
||||
pass
|
||||
if self.__extra_names__:
|
||||
locals = {**locals, **self.__extra_names__}
|
||||
|
||||
|
|
@ -202,7 +212,7 @@ def evaluate(
|
|||
except Exception:
|
||||
return self
|
||||
else:
|
||||
new_locals.transmogrify()
|
||||
new_locals.transmogrify(self.__cell__)
|
||||
return result
|
||||
|
||||
def _evaluate(self, globalns, localns, type_params=_sentinel, *, recursive_guard):
|
||||
|
|
@ -274,7 +284,7 @@ def __hash__(self):
|
|||
self.__forward_module__,
|
||||
id(self.__globals__), # dictionaries are not hashable, so hash by identity
|
||||
self.__forward_is_class__,
|
||||
self.__cell__,
|
||||
tuple(sorted(self.__cell__.items())) if isinstance(self.__cell__, dict) else self.__cell__,
|
||||
self.__owner__,
|
||||
tuple(sorted(self.__extra_names__.items())) if self.__extra_names__ else None,
|
||||
))
|
||||
|
|
@ -642,13 +652,15 @@ def __missing__(self, key):
|
|||
self.stringifiers.append(fwdref)
|
||||
return fwdref
|
||||
|
||||
def transmogrify(self):
|
||||
def transmogrify(self, cell_dict):
|
||||
for obj in self.stringifiers:
|
||||
obj.__class__ = ForwardRef
|
||||
obj.__stringifier_dict__ = None # not needed for ForwardRef
|
||||
if isinstance(obj.__ast_node__, str):
|
||||
obj.__arg__ = obj.__ast_node__
|
||||
obj.__ast_node__ = None
|
||||
if cell_dict is not None and obj.__cell__ is None:
|
||||
obj.__cell__ = cell_dict
|
||||
|
||||
def create_unique_name(self):
|
||||
name = f"__annotationlib_name_{self.next_id}__"
|
||||
|
|
@ -712,7 +724,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
|
|||
|
||||
globals = _StringifierDict({}, format=format)
|
||||
is_class = isinstance(owner, type)
|
||||
closure = _build_closure(
|
||||
closure, _ = _build_closure(
|
||||
annotate, owner, is_class, globals, allow_evaluation=False
|
||||
)
|
||||
func = types.FunctionType(
|
||||
|
|
@ -756,7 +768,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
|
|||
is_class=is_class,
|
||||
format=format,
|
||||
)
|
||||
closure = _build_closure(
|
||||
closure, cell_dict = _build_closure(
|
||||
annotate, owner, is_class, globals, allow_evaluation=True
|
||||
)
|
||||
func = types.FunctionType(
|
||||
|
|
@ -774,7 +786,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
|
|||
except Exception:
|
||||
pass
|
||||
else:
|
||||
globals.transmogrify()
|
||||
globals.transmogrify(cell_dict)
|
||||
return result
|
||||
|
||||
# Try again, but do not provide any globals. This allows us to return
|
||||
|
|
@ -786,7 +798,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
|
|||
is_class=is_class,
|
||||
format=format,
|
||||
)
|
||||
closure = _build_closure(
|
||||
closure, cell_dict = _build_closure(
|
||||
annotate, owner, is_class, globals, allow_evaluation=False
|
||||
)
|
||||
func = types.FunctionType(
|
||||
|
|
@ -797,7 +809,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
|
|||
kwdefaults=annotate.__kwdefaults__,
|
||||
)
|
||||
result = func(Format.VALUE_WITH_FAKE_GLOBALS)
|
||||
globals.transmogrify()
|
||||
globals.transmogrify(cell_dict)
|
||||
if _is_evaluate:
|
||||
if isinstance(result, ForwardRef):
|
||||
return result.evaluate(format=Format.FORWARDREF)
|
||||
|
|
@ -822,14 +834,16 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
|
|||
|
||||
def _build_closure(annotate, owner, is_class, stringifier_dict, *, allow_evaluation):
|
||||
if not annotate.__closure__:
|
||||
return None
|
||||
return None, None
|
||||
freevars = annotate.__code__.co_freevars
|
||||
new_closure = []
|
||||
cell_dict = {}
|
||||
for i, cell in enumerate(annotate.__closure__):
|
||||
if i < len(freevars):
|
||||
name = freevars[i]
|
||||
else:
|
||||
name = "__cell__"
|
||||
cell_dict[name] = cell
|
||||
new_cell = None
|
||||
if allow_evaluation:
|
||||
try:
|
||||
|
|
@ -850,7 +864,7 @@ def _build_closure(annotate, owner, is_class, stringifier_dict, *, allow_evaluat
|
|||
stringifier_dict.stringifiers.append(fwdref)
|
||||
new_cell = types.CellType(fwdref)
|
||||
new_closure.append(new_cell)
|
||||
return tuple(new_closure)
|
||||
return tuple(new_closure), cell_dict
|
||||
|
||||
|
||||
def _stringify_single(anno):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue