Skip to content

Commit dcf6292

Browse files
gh-119180: Add VALUE_WITH_FAKE_GLOBALS format to annotationlib (#124415)
1 parent 2b0e2b2 commit dcf6292

File tree

8 files changed

+73
-26
lines changed

8 files changed

+73
-26
lines changed

Doc/library/annotationlib.rst

+11
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,17 @@ Classes
144144

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

147+
.. attribute:: VALUE_WITH_FAKE_GLOBALS
148+
:value: 4
149+
150+
Special value used to signal that an annotate function is being
151+
evaluated in a special environment with fake globals. When passed this
152+
value, annotate functions should either return the same value as for
153+
the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
154+
to signal that they do not support execution in this environment.
155+
This format is only used internally and should not be passed to
156+
the functions in this module.
157+
147158
.. versionadded:: 3.14
148159

149160
.. class:: ForwardRef

Include/internal/pycore_object.h

+7
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,13 @@ PyAPI_DATA(int) _Py_SwappedOp[];
919919

920920
extern void _Py_GetConstant_Init(void);
921921

922+
enum _PyAnnotateFormat {
923+
_Py_ANNOTATE_FORMAT_VALUE = 1,
924+
_Py_ANNOTATE_FORMAT_VALUE_WITH_FAKE_GLOBALS = 2,
925+
_Py_ANNOTATE_FORMAT_FORWARDREF = 3,
926+
_Py_ANNOTATE_FORMAT_STRING = 4,
927+
};
928+
922929
#ifdef __cplusplus
923930
}
924931
#endif

Lib/annotationlib.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222

2323
class Format(enum.IntEnum):
2424
VALUE = 1
25-
FORWARDREF = 2
26-
STRING = 3
25+
VALUE_WITH_FAKE_GLOBALS = 2
26+
FORWARDREF = 3
27+
STRING = 4
2728

2829

2930
_Union = None
@@ -513,6 +514,8 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
513514
on the generated ForwardRef objects.
514515
515516
"""
517+
if format == Format.VALUE_WITH_FAKE_GLOBALS:
518+
raise ValueError("The VALUE_WITH_FAKE_GLOBALS format is for internal use only")
516519
try:
517520
return annotate(format)
518521
except NotImplementedError:
@@ -546,7 +549,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
546549
argdefs=annotate.__defaults__,
547550
kwdefaults=annotate.__kwdefaults__,
548551
)
549-
annos = func(Format.VALUE)
552+
annos = func(Format.VALUE_WITH_FAKE_GLOBALS)
550553
if _is_evaluate:
551554
return annos if isinstance(annos, str) else repr(annos)
552555
return {
@@ -607,7 +610,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
607610
argdefs=annotate.__defaults__,
608611
kwdefaults=annotate.__kwdefaults__,
609612
)
610-
result = func(Format.VALUE)
613+
result = func(Format.VALUE_WITH_FAKE_GLOBALS)
611614
for obj in globals.stringifiers:
612615
obj.__class__ = ForwardRef
613616
obj.__stringifier_dict__ = None # not needed for ForwardRef
@@ -726,6 +729,8 @@ def get_annotations(
726729
# But if we didn't get it, we use __annotations__ instead.
727730
ann = _get_dunder_annotations(obj)
728731
return annotations_to_string(ann)
732+
case Format.VALUE_WITH_FAKE_GLOBALS:
733+
raise ValueError("The VALUE_WITH_FAKE_GLOBALS format is for internal use only")
729734
case _:
730735
raise ValueError(f"Unsupported format {format!r}")
731736

Lib/test/test_annotationlib.py

+26-10
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ def test_enum(self):
4242
self.assertEqual(Format.VALUE.value, 1)
4343
self.assertEqual(Format.VALUE, 1)
4444

45-
self.assertEqual(Format.FORWARDREF.value, 2)
46-
self.assertEqual(Format.FORWARDREF, 2)
45+
self.assertEqual(Format.VALUE_WITH_FAKE_GLOBALS.value, 2)
46+
self.assertEqual(Format.VALUE_WITH_FAKE_GLOBALS, 2)
4747

48-
self.assertEqual(Format.STRING.value, 3)
49-
self.assertEqual(Format.STRING, 3)
48+
self.assertEqual(Format.FORWARDREF.value, 3)
49+
self.assertEqual(Format.FORWARDREF, 3)
50+
51+
self.assertEqual(Format.STRING.value, 4)
52+
self.assertEqual(Format.STRING, 4)
5053

5154

5255
class TestForwardRefFormat(unittest.TestCase):
@@ -459,19 +462,28 @@ def f2(a: undefined):
459462
annotationlib.get_annotations(f2, format=Format.FORWARDREF),
460463
{"a": fwd},
461464
)
462-
self.assertEqual(annotationlib.get_annotations(f2, format=2), {"a": fwd})
465+
self.assertEqual(annotationlib.get_annotations(f2, format=3), {"a": fwd})
463466

464467
self.assertEqual(
465468
annotationlib.get_annotations(f1, format=Format.STRING),
466469
{"a": "int"},
467470
)
468-
self.assertEqual(annotationlib.get_annotations(f1, format=3), {"a": "int"})
471+
self.assertEqual(annotationlib.get_annotations(f1, format=4), {"a": "int"})
469472

470473
with self.assertRaises(ValueError):
471-
annotationlib.get_annotations(f1, format=0)
474+
annotationlib.get_annotations(f1, format=42)
472475

473-
with self.assertRaises(ValueError):
474-
annotationlib.get_annotations(f1, format=4)
476+
with self.assertRaisesRegex(
477+
ValueError,
478+
r"The VALUE_WITH_FAKE_GLOBALS format is for internal use only",
479+
):
480+
annotationlib.get_annotations(f1, format=Format.VALUE_WITH_FAKE_GLOBALS)
481+
482+
with self.assertRaisesRegex(
483+
ValueError,
484+
r"The VALUE_WITH_FAKE_GLOBALS format is for internal use only",
485+
):
486+
annotationlib.get_annotations(f1, format=2)
475487

476488
def test_custom_object_with_annotations(self):
477489
class C:
@@ -505,6 +517,8 @@ def foo(a: int, b: str):
505517

506518
foo.__annotations__ = {"a": "foo", "b": "str"}
507519
for format in Format:
520+
if format is Format.VALUE_WITH_FAKE_GLOBALS:
521+
continue
508522
with self.subTest(format=format):
509523
self.assertEqual(
510524
annotationlib.get_annotations(foo, format=format),
@@ -802,6 +816,8 @@ def __annotations__(self):
802816

803817
wa = WeirdAnnotations()
804818
for format in Format:
819+
if format is Format.VALUE_WITH_FAKE_GLOBALS:
820+
continue
805821
with (
806822
self.subTest(format=format),
807823
self.assertRaisesRegex(
@@ -990,7 +1006,7 @@ def test_pep_695_generics_with_future_annotations_nested_in_function(self):
9901006
class TestCallEvaluateFunction(unittest.TestCase):
9911007
def test_evaluation(self):
9921008
def evaluate(format, exc=NotImplementedError):
993-
if format != 1:
1009+
if format > 2:
9941010
raise exc
9951011
return undefined
9961012

Lib/test/test_type_annotations.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def test_module(self):
316316
ns = run_code("x: undefined = 1")
317317
anno = ns["__annotate__"]
318318
with self.assertRaises(NotImplementedError):
319-
anno(2)
319+
anno(3)
320320

321321
with self.assertRaises(NameError):
322322
anno(1)
@@ -376,7 +376,7 @@ class X:
376376
annotate(annotationlib.Format.FORWARDREF)
377377
with self.assertRaises(NotImplementedError):
378378
annotate(annotationlib.Format.STRING)
379-
with self.assertRaises(NotImplementedError):
379+
with self.assertRaises(TypeError):
380380
annotate(None)
381381
self.assertEqual(annotate(annotationlib.Format.VALUE), {"x": int})
382382

Lib/typing.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -2936,10 +2936,13 @@ def _make_eager_annotate(types):
29362936
checked_types = {key: _type_check(val, f"field {key} annotation must be a type")
29372937
for key, val in types.items()}
29382938
def annotate(format):
2939-
if format in (annotationlib.Format.VALUE, annotationlib.Format.FORWARDREF):
2940-
return checked_types
2941-
else:
2942-
return annotationlib.annotations_to_string(types)
2939+
match format:
2940+
case annotationlib.Format.VALUE | annotationlib.Format.FORWARDREF:
2941+
return checked_types
2942+
case annotationlib.Format.STRING:
2943+
return annotationlib.annotations_to_string(types)
2944+
case _:
2945+
raise NotImplementedError(format)
29432946
return annotate
29442947

29452948

@@ -3229,8 +3232,10 @@ def __annotate__(format):
32293232
}
32303233
elif format == annotationlib.Format.STRING:
32313234
own = annotationlib.annotations_to_string(own_annotations)
3232-
else:
3235+
elif format in (annotationlib.Format.FORWARDREF, annotationlib.Format.VALUE):
32333236
own = own_checked_annotations
3237+
else:
3238+
raise NotImplementedError(format)
32343239
annos.update(own)
32353240
return annos
32363241

Objects/typevarobject.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// TypeVar, TypeVarTuple, and ParamSpec
22
#include "Python.h"
3-
#include "pycore_object.h" // _PyObject_GC_TRACK/UNTRACK
3+
#include "pycore_object.h" // _PyObject_GC_TRACK/UNTRACK, PyAnnotateFormat
44
#include "pycore_typevarobject.h"
55
#include "pycore_unionobject.h" // _Py_union_type_or
66

@@ -168,7 +168,7 @@ constevaluator_call(PyObject *self, PyObject *args, PyObject *kwargs)
168168
return NULL;
169169
}
170170
PyObject *value = ((constevaluatorobject *)self)->value;
171-
if (format == 3) { // STRING
171+
if (format == _Py_ANNOTATE_FORMAT_STRING) {
172172
PyUnicodeWriter *writer = PyUnicodeWriter_Create(5); // cannot be <5
173173
if (writer == NULL) {
174174
return NULL;

Python/codegen.c

+6-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "pycore_instruction_sequence.h" // _PyInstructionSequence_NewLabel()
2525
#include "pycore_intrinsics.h"
2626
#include "pycore_long.h" // _PyLong_GetZero()
27+
#include "pycore_object.h" // _Py_ANNOTATE_FORMAT_VALUE_WITH_FAKE_GLOBALS
2728
#include "pycore_pystate.h" // _Py_GetConfig()
2829
#include "pycore_symtable.h" // PySTEntryObject
2930

@@ -672,14 +673,16 @@ codegen_setup_annotations_scope(compiler *c, location loc,
672673
codegen_enter_scope(c, name, COMPILE_SCOPE_ANNOTATIONS,
673674
key, loc.lineno, NULL, &umd));
674675

676+
// if .format > VALUE_WITH_FAKE_GLOBALS: raise NotImplementedError
677+
PyObject *value_with_fake_globals = PyLong_FromLong(_Py_ANNOTATE_FORMAT_VALUE_WITH_FAKE_GLOBALS);
675678
assert(!SYMTABLE_ENTRY(c)->ste_has_docstring);
676-
// if .format != 1: raise NotImplementedError
677679
_Py_DECLARE_STR(format, ".format");
678680
ADDOP_I(c, loc, LOAD_FAST, 0);
679-
ADDOP_LOAD_CONST(c, loc, _PyLong_GetOne());
680-
ADDOP_I(c, loc, COMPARE_OP, (Py_NE << 5) | compare_masks[Py_NE]);
681+
ADDOP_LOAD_CONST(c, loc, value_with_fake_globals);
682+
ADDOP_I(c, loc, COMPARE_OP, (Py_GT << 5) | compare_masks[Py_GT]);
681683
NEW_JUMP_TARGET_LABEL(c, body);
682684
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, body);
685+
683686
ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_NOTIMPLEMENTEDERROR);
684687
ADDOP_I(c, loc, RAISE_VARARGS, 1);
685688
USE_LABEL(c, body);

0 commit comments

Comments
 (0)