Skip to content

Commit

Permalink
pythongh-119180: Add VALUE_WITH_FAKE_GLOBALS format to annotationlib
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra committed Sep 24, 2024
1 parent d56faf2 commit ddacdb7
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 9 deletions.
11 changes: 11 additions & 0 deletions Doc/library/annotationlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ Classes

The exact values of these strings may change in future versions of Python.

.. attribute:: VALUE_WITH_FAKE_GLOBALS
:value: 4

Special value used to signal that an annotate function is being
evaluated in a special environment with fake globals. When passed this
value, annotate functions should either return the same value as for
the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
to signal that they do not support execution in this environment.
This format is only used internally and should not be passed to
the functions in this module.

.. versionadded:: 3.14

.. class:: ForwardRef
Expand Down
7 changes: 5 additions & 2 deletions Lib/annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Format(enum.IntEnum):
VALUE = 1
FORWARDREF = 2
SOURCE = 3
VALUE_WITH_FAKE_GLOBALS = 4


_Union = None
Expand Down Expand Up @@ -459,6 +460,8 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
on the generated ForwardRef objects.
"""
if format == Format.VALUE_WITH_FAKE_GLOBALS:
raise ValueError("The VALUE_WITH_FAKE_GLOBALS format is for internal use only")
try:
return annotate(format)
except NotImplementedError:
Expand Down Expand Up @@ -492,7 +495,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
argdefs=annotate.__defaults__,
kwdefaults=annotate.__kwdefaults__,
)
annos = func(Format.VALUE)
annos = func(Format.VALUE_WITH_FAKE_GLOBALS)
if _is_evaluate:
return annos if isinstance(annos, str) else repr(annos)
return {
Expand Down Expand Up @@ -552,7 +555,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
argdefs=annotate.__defaults__,
kwdefaults=annotate.__kwdefaults__,
)
result = func(Format.VALUE)
result = func(Format.VALUE_WITH_FAKE_GLOBALS)
for obj in globals.stringifiers:
obj.__class__ = ForwardRef
return result
Expand Down
14 changes: 13 additions & 1 deletion Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,18 @@ def f2(a: undefined):
annotationlib.get_annotations(f1, format=0)

with self.assertRaises(ValueError):
annotationlib.get_annotations(f1, format=42)

with self.assertRaisesRegex(
ValueError,
r"The VALUE_WITH_FAKE_GLOBALS format is for internal use only",
):
annotationlib.get_annotations(f1, format=Format.VALUE_WITH_FAKE_GLOBALS)

with self.assertRaisesRegex(
ValueError,
r"The VALUE_WITH_FAKE_GLOBALS format is for internal use only",
):
annotationlib.get_annotations(f1, format=4)

def test_custom_object_with_annotations(self):
Expand Down Expand Up @@ -840,7 +852,7 @@ def test_pep_695_generics_with_future_annotations_nested_in_function(self):
class TestCallEvaluateFunction(unittest.TestCase):
def test_evaluation(self):
def evaluate(format, exc=NotImplementedError):
if format != 1:
if format != 1 and format != 4:
raise exc
return undefined

Expand Down
15 changes: 10 additions & 5 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2945,10 +2945,13 @@ def _make_eager_annotate(types):
checked_types = {key: _type_check(val, f"field {key} annotation must be a type")
for key, val in types.items()}
def annotate(format):
if format in (annotationlib.Format.VALUE, annotationlib.Format.FORWARDREF):
return checked_types
else:
return _convert_to_source(types)
match format:
case annotationlib.Format.VALUE | annotationlib.Format.FORWARDREF:
return checked_types
case annotationlib.Format.SOURCE:
return _convert_to_source(types)
case _:
raise NotImplementedError(format)
return annotate


Expand Down Expand Up @@ -3242,8 +3245,10 @@ def __annotate__(format):
}
elif format == annotationlib.Format.SOURCE:
own = _convert_to_source(own_annotations)
else:
elif format in (annotationlib.Format.FORWARDREF, annotationlib.Format.VALUE):
own = own_checked_annotations
else:
raise NotImplementedError(format)
annos.update(own)
return annos

Expand Down
10 changes: 9 additions & 1 deletion Python/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -655,13 +655,21 @@ codegen_setup_annotations_scope(compiler *c, location loc,
codegen_enter_scope(c, name, COMPILE_SCOPE_ANNOTATIONS,
key, loc.lineno, NULL, &umd));

// if .format != 1: raise NotImplementedError
// if .format != 1 and .format != 4: raise NotImplementedError
_Py_DECLARE_STR(format, ".format");
ADDOP_I(c, loc, LOAD_FAST, 0);
ADDOP_LOAD_CONST(c, loc, _PyLong_GetOne());
ADDOP_I(c, loc, COMPARE_OP, (Py_NE << 5) | compare_masks[Py_NE]);
NEW_JUMP_TARGET_LABEL(c, body);
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, body);

ADDOP_I(c, loc, LOAD_FAST, 0);
PyObject *four = PyLong_FromLong(4);
assert(four != NULL);
ADDOP_LOAD_CONST(c, loc, four);
ADDOP_I(c, loc, COMPARE_OP, (Py_NE << 5) | compare_masks[Py_NE]);
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, body);

ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_NOTIMPLEMENTEDERROR);
ADDOP_I(c, loc, RAISE_VARARGS, 1);
USE_LABEL(c, body);
Expand Down

0 comments on commit ddacdb7

Please sign in to comment.