Skip to content

Commit 251007d

Browse files
committed
Merge remote-tracking branch 'origin/main' into call-len
* origin/main: pythongh-125618: Make FORWARDREF format succeed more often (python#132818)
2 parents 2e6d608 + af5799f commit 251007d

File tree

4 files changed

+237
-63
lines changed

4 files changed

+237
-63
lines changed

Doc/library/annotationlib.rst

+11-4
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ Classes
132132

133133
Values are real annotation values (as per :attr:`Format.VALUE` format)
134134
for defined values, and :class:`ForwardRef` proxies for undefined
135-
values. Real objects may contain references to, :class:`ForwardRef`
135+
values. Real objects may contain references to :class:`ForwardRef`
136136
proxy objects.
137137

138138
.. attribute:: STRING
@@ -172,14 +172,21 @@ Classes
172172
:class:`~ForwardRef`. The string may not be exactly equivalent
173173
to the original source.
174174

175-
.. method:: evaluate(*, owner=None, globals=None, locals=None, type_params=None)
175+
.. method:: evaluate(*, owner=None, globals=None, locals=None, type_params=None, format=Format.VALUE)
176176

177177
Evaluate the forward reference, returning its value.
178178

179-
This may throw an exception, such as :exc:`NameError`, if the forward
179+
If the *format* argument is :attr:`~Format.VALUE` (the default),
180+
this method may throw an exception, such as :exc:`NameError`, if the forward
180181
reference refers to a name that cannot be resolved. The arguments to this
181182
method can be used to provide bindings for names that would otherwise
182-
be undefined.
183+
be undefined. If the *format* argument is :attr:`~Format.FORWARDREF`,
184+
the method will never throw an exception, but may return a :class:`~ForwardRef`
185+
instance. For example, if the forward reference object contains the code
186+
``list[undefined]``, where ``undefined`` is a name that is not defined,
187+
evaluating it with the :attr:`~Format.FORWARDREF` format will return
188+
``list[ForwardRef('undefined')]``. If the *format* argument is
189+
:attr:`~Format.STRING`, the method will return :attr:`~ForwardRef.__forward_arg__`.
183190

184191
The *owner* parameter provides the preferred mechanism for passing scope
185192
information to this method. The owner of a :class:`~ForwardRef` is the

Lib/annotationlib.py

+131-51
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,28 @@ def __init__(
9292
def __init_subclass__(cls, /, *args, **kwds):
9393
raise TypeError("Cannot subclass ForwardRef")
9494

95-
def evaluate(self, *, globals=None, locals=None, type_params=None, owner=None):
95+
def evaluate(
96+
self,
97+
*,
98+
globals=None,
99+
locals=None,
100+
type_params=None,
101+
owner=None,
102+
format=Format.VALUE,
103+
):
96104
"""Evaluate the forward reference and return the value.
97105
98106
If the forward reference cannot be evaluated, raise an exception.
99107
"""
108+
match format:
109+
case Format.STRING:
110+
return self.__forward_arg__
111+
case Format.VALUE:
112+
is_forwardref_format = False
113+
case Format.FORWARDREF:
114+
is_forwardref_format = True
115+
case _:
116+
raise NotImplementedError(format)
100117
if self.__cell__ is not None:
101118
try:
102119
return self.__cell__.cell_contents
@@ -159,17 +176,36 @@ def evaluate(self, *, globals=None, locals=None, type_params=None, owner=None):
159176
arg = self.__forward_arg__
160177
if arg.isidentifier() and not keyword.iskeyword(arg):
161178
if arg in locals:
162-
value = locals[arg]
179+
return locals[arg]
163180
elif arg in globals:
164-
value = globals[arg]
181+
return globals[arg]
165182
elif hasattr(builtins, arg):
166183
return getattr(builtins, arg)
184+
elif is_forwardref_format:
185+
return self
167186
else:
168187
raise NameError(arg)
169188
else:
170189
code = self.__forward_code__
171-
value = eval(code, globals=globals, locals=locals)
172-
return value
190+
try:
191+
return eval(code, globals=globals, locals=locals)
192+
except Exception:
193+
if not is_forwardref_format:
194+
raise
195+
new_locals = _StringifierDict(
196+
{**builtins.__dict__, **locals},
197+
globals=globals,
198+
owner=owner,
199+
is_class=self.__forward_is_class__,
200+
format=format,
201+
)
202+
try:
203+
result = eval(code, globals=globals, locals=new_locals)
204+
except Exception:
205+
return self
206+
else:
207+
new_locals.transmogrify()
208+
return result
173209

174210
def _evaluate(self, globalns, localns, type_params=_sentinel, *, recursive_guard):
175211
import typing
@@ -546,6 +582,14 @@ def __missing__(self, key):
546582
self.stringifiers.append(fwdref)
547583
return fwdref
548584

585+
def transmogrify(self):
586+
for obj in self.stringifiers:
587+
obj.__class__ = ForwardRef
588+
obj.__stringifier_dict__ = None # not needed for ForwardRef
589+
if isinstance(obj.__ast_node__, str):
590+
obj.__arg__ = obj.__ast_node__
591+
obj.__ast_node__ = None
592+
549593
def create_unique_name(self):
550594
name = f"__annotationlib_name_{self.next_id}__"
551595
self.next_id += 1
@@ -595,19 +639,10 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
595639
# convert each of those into a string to get an approximation of the
596640
# original source.
597641
globals = _StringifierDict({}, format=format)
598-
if annotate.__closure__:
599-
freevars = annotate.__code__.co_freevars
600-
new_closure = []
601-
for i, cell in enumerate(annotate.__closure__):
602-
if i < len(freevars):
603-
name = freevars[i]
604-
else:
605-
name = "__cell__"
606-
fwdref = _Stringifier(name, stringifier_dict=globals)
607-
new_closure.append(types.CellType(fwdref))
608-
closure = tuple(new_closure)
609-
else:
610-
closure = None
642+
is_class = isinstance(owner, type)
643+
closure = _build_closure(
644+
annotate, owner, is_class, globals, allow_evaluation=False
645+
)
611646
func = types.FunctionType(
612647
annotate.__code__,
613648
globals,
@@ -649,32 +684,36 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
649684
is_class=is_class,
650685
format=format,
651686
)
652-
if annotate.__closure__:
653-
freevars = annotate.__code__.co_freevars
654-
new_closure = []
655-
for i, cell in enumerate(annotate.__closure__):
656-
try:
657-
cell.cell_contents
658-
except ValueError:
659-
if i < len(freevars):
660-
name = freevars[i]
661-
else:
662-
name = "__cell__"
663-
fwdref = _Stringifier(
664-
name,
665-
cell=cell,
666-
owner=owner,
667-
globals=annotate.__globals__,
668-
is_class=is_class,
669-
stringifier_dict=globals,
670-
)
671-
globals.stringifiers.append(fwdref)
672-
new_closure.append(types.CellType(fwdref))
673-
else:
674-
new_closure.append(cell)
675-
closure = tuple(new_closure)
687+
closure = _build_closure(
688+
annotate, owner, is_class, globals, allow_evaluation=True
689+
)
690+
func = types.FunctionType(
691+
annotate.__code__,
692+
globals,
693+
closure=closure,
694+
argdefs=annotate.__defaults__,
695+
kwdefaults=annotate.__kwdefaults__,
696+
)
697+
try:
698+
result = func(Format.VALUE_WITH_FAKE_GLOBALS)
699+
except Exception:
700+
pass
676701
else:
677-
closure = None
702+
globals.transmogrify()
703+
return result
704+
705+
# Try again, but do not provide any globals. This allows us to return
706+
# a value in certain cases where an exception gets raised during evaluation.
707+
globals = _StringifierDict(
708+
{},
709+
globals=annotate.__globals__,
710+
owner=owner,
711+
is_class=is_class,
712+
format=format,
713+
)
714+
closure = _build_closure(
715+
annotate, owner, is_class, globals, allow_evaluation=False
716+
)
678717
func = types.FunctionType(
679718
annotate.__code__,
680719
globals,
@@ -683,13 +722,21 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
683722
kwdefaults=annotate.__kwdefaults__,
684723
)
685724
result = func(Format.VALUE_WITH_FAKE_GLOBALS)
686-
for obj in globals.stringifiers:
687-
obj.__class__ = ForwardRef
688-
obj.__stringifier_dict__ = None # not needed for ForwardRef
689-
if isinstance(obj.__ast_node__, str):
690-
obj.__arg__ = obj.__ast_node__
691-
obj.__ast_node__ = None
692-
return result
725+
globals.transmogrify()
726+
if _is_evaluate:
727+
if isinstance(result, ForwardRef):
728+
return result.evaluate(format=Format.FORWARDREF)
729+
else:
730+
return result
731+
else:
732+
return {
733+
key: (
734+
val.evaluate(format=Format.FORWARDREF)
735+
if isinstance(val, ForwardRef)
736+
else val
737+
)
738+
for key, val in result.items()
739+
}
693740
elif format == Format.VALUE:
694741
# Should be impossible because __annotate__ functions must not raise
695742
# NotImplementedError for this format.
@@ -698,6 +745,39 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
698745
raise ValueError(f"Invalid format: {format!r}")
699746

700747

748+
def _build_closure(annotate, owner, is_class, stringifier_dict, *, allow_evaluation):
749+
if not annotate.__closure__:
750+
return None
751+
freevars = annotate.__code__.co_freevars
752+
new_closure = []
753+
for i, cell in enumerate(annotate.__closure__):
754+
if i < len(freevars):
755+
name = freevars[i]
756+
else:
757+
name = "__cell__"
758+
new_cell = None
759+
if allow_evaluation:
760+
try:
761+
cell.cell_contents
762+
except ValueError:
763+
pass
764+
else:
765+
new_cell = cell
766+
if new_cell is None:
767+
fwdref = _Stringifier(
768+
name,
769+
cell=cell,
770+
owner=owner,
771+
globals=annotate.__globals__,
772+
is_class=is_class,
773+
stringifier_dict=stringifier_dict,
774+
)
775+
stringifier_dict.stringifiers.append(fwdref)
776+
new_cell = types.CellType(fwdref)
777+
new_closure.append(new_cell)
778+
return tuple(new_closure)
779+
780+
701781
def _stringify_single(anno):
702782
if anno is ...:
703783
return "..."
@@ -809,7 +889,7 @@ def get_annotations(
809889
# But if we didn't get it, we use __annotations__ instead.
810890
ann = _get_dunder_annotations(obj)
811891
if ann is not None:
812-
return annotations_to_string(ann)
892+
return annotations_to_string(ann)
813893
case Format.VALUE_WITH_FAKE_GLOBALS:
814894
raise ValueError("The VALUE_WITH_FAKE_GLOBALS format is for internal use only")
815895
case _:

0 commit comments

Comments
 (0)