# 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
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:
# 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__}
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):
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,
))
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}__"
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(
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(
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
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(
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)
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:
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):