https://github.com/python/cpython/commit/dcf629213bc046318c862ec0af5db3dfd1fc473a
commit: dcf629213bc046318c862ec0af5db3dfd1fc473a
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2024-11-26T15:40:13Z
summary:

gh-119180: Add VALUE_WITH_FAKE_GLOBALS format to annotationlib (#124415)

files:
M Doc/library/annotationlib.rst
M Include/internal/pycore_object.h
M Lib/annotationlib.py
M Lib/test/test_annotationlib.py
M Lib/test/test_type_annotations.py
M Lib/typing.py
M Objects/typevarobject.c
M Python/codegen.c

diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst
index 37490456d13312..dcaff3d7fdbec5 100644
--- a/Doc/library/annotationlib.rst
+++ b/Doc/library/annotationlib.rst
@@ -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
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 783d88cb51ffbd..34d835a7f84ee7 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -919,6 +919,13 @@ PyAPI_DATA(int) _Py_SwappedOp[];
 
 extern void _Py_GetConstant_Init(void);
 
+enum _PyAnnotateFormat {
+    _Py_ANNOTATE_FORMAT_VALUE = 1,
+    _Py_ANNOTATE_FORMAT_VALUE_WITH_FAKE_GLOBALS = 2,
+    _Py_ANNOTATE_FORMAT_FORWARDREF = 3,
+    _Py_ANNOTATE_FORMAT_STRING = 4,
+};
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 732fbfa628cf5f..42f1f3877514d9 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -22,8 +22,9 @@
 
 class Format(enum.IntEnum):
     VALUE = 1
-    FORWARDREF = 2
-    STRING = 3
+    VALUE_WITH_FAKE_GLOBALS = 2
+    FORWARDREF = 3
+    STRING = 4
 
 
 _Union = None
@@ -513,6 +514,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:
@@ -546,7 +549,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 {
@@ -607,7 +610,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
             obj.__stringifier_dict__ = None  # not needed for ForwardRef
@@ -726,6 +729,8 @@ def get_annotations(
             # But if we didn't get it, we use __annotations__ instead.
             ann = _get_dunder_annotations(obj)
             return annotations_to_string(ann)
+        case Format.VALUE_WITH_FAKE_GLOBALS:
+            raise ValueError("The VALUE_WITH_FAKE_GLOBALS format is for 
internal use only")
         case _:
             raise ValueError(f"Unsupported format {format!r}")
 
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index 2ca7058c14398c..20f74b4ed0aadb 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -42,11 +42,14 @@ def test_enum(self):
         self.assertEqual(Format.VALUE.value, 1)
         self.assertEqual(Format.VALUE, 1)
 
-        self.assertEqual(Format.FORWARDREF.value, 2)
-        self.assertEqual(Format.FORWARDREF, 2)
+        self.assertEqual(Format.VALUE_WITH_FAKE_GLOBALS.value, 2)
+        self.assertEqual(Format.VALUE_WITH_FAKE_GLOBALS, 2)
 
-        self.assertEqual(Format.STRING.value, 3)
-        self.assertEqual(Format.STRING, 3)
+        self.assertEqual(Format.FORWARDREF.value, 3)
+        self.assertEqual(Format.FORWARDREF, 3)
+
+        self.assertEqual(Format.STRING.value, 4)
+        self.assertEqual(Format.STRING, 4)
 
 
 class TestForwardRefFormat(unittest.TestCase):
@@ -459,19 +462,28 @@ def f2(a: undefined):
             annotationlib.get_annotations(f2, format=Format.FORWARDREF),
             {"a": fwd},
         )
-        self.assertEqual(annotationlib.get_annotations(f2, format=2), {"a": 
fwd})
+        self.assertEqual(annotationlib.get_annotations(f2, format=3), {"a": 
fwd})
 
         self.assertEqual(
             annotationlib.get_annotations(f1, format=Format.STRING),
             {"a": "int"},
         )
-        self.assertEqual(annotationlib.get_annotations(f1, format=3), {"a": 
"int"})
+        self.assertEqual(annotationlib.get_annotations(f1, format=4), {"a": 
"int"})
 
         with self.assertRaises(ValueError):
-            annotationlib.get_annotations(f1, format=0)
+            annotationlib.get_annotations(f1, format=42)
 
-        with self.assertRaises(ValueError):
-            annotationlib.get_annotations(f1, format=4)
+        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=2)
 
     def test_custom_object_with_annotations(self):
         class C:
@@ -505,6 +517,8 @@ def foo(a: int, b: str):
 
         foo.__annotations__ = {"a": "foo", "b": "str"}
         for format in Format:
+            if format is Format.VALUE_WITH_FAKE_GLOBALS:
+                continue
             with self.subTest(format=format):
                 self.assertEqual(
                     annotationlib.get_annotations(foo, format=format),
@@ -802,6 +816,8 @@ def __annotations__(self):
 
         wa = WeirdAnnotations()
         for format in Format:
+            if format is Format.VALUE_WITH_FAKE_GLOBALS:
+                continue
             with (
                 self.subTest(format=format),
                 self.assertRaisesRegex(
@@ -990,7 +1006,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 > 2:
                 raise exc
             return undefined
 
diff --git a/Lib/test/test_type_annotations.py 
b/Lib/test/test_type_annotations.py
index 257b7fa95dcb76..7d88f4cdfa3141 100644
--- a/Lib/test/test_type_annotations.py
+++ b/Lib/test/test_type_annotations.py
@@ -316,7 +316,7 @@ def test_module(self):
         ns = run_code("x: undefined = 1")
         anno = ns["__annotate__"]
         with self.assertRaises(NotImplementedError):
-            anno(2)
+            anno(3)
 
         with self.assertRaises(NameError):
             anno(1)
@@ -376,7 +376,7 @@ class X:
                     annotate(annotationlib.Format.FORWARDREF)
                 with self.assertRaises(NotImplementedError):
                     annotate(annotationlib.Format.STRING)
-                with self.assertRaises(NotImplementedError):
+                with self.assertRaises(TypeError):
                     annotate(None)
                 self.assertEqual(annotate(annotationlib.Format.VALUE), {"x": 
int})
 
diff --git a/Lib/typing.py b/Lib/typing.py
index 938e52922aee03..5f3aacd877221c 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -2936,10 +2936,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 annotationlib.annotations_to_string(types)
+        match format:
+            case annotationlib.Format.VALUE | annotationlib.Format.FORWARDREF:
+                return checked_types
+            case annotationlib.Format.STRING:
+                return annotationlib.annotations_to_string(types)
+            case _:
+                raise NotImplementedError(format)
     return annotate
 
 
@@ -3229,8 +3232,10 @@ def __annotate__(format):
                     }
             elif format == annotationlib.Format.STRING:
                 own = annotationlib.annotations_to_string(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
 
diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c
index bacb858978c5d7..4ed40aa71a595e 100644
--- a/Objects/typevarobject.c
+++ b/Objects/typevarobject.c
@@ -1,6 +1,6 @@
 // TypeVar, TypeVarTuple, and ParamSpec
 #include "Python.h"
-#include "pycore_object.h"        // _PyObject_GC_TRACK/UNTRACK
+#include "pycore_object.h"        // _PyObject_GC_TRACK/UNTRACK, 
PyAnnotateFormat
 #include "pycore_typevarobject.h"
 #include "pycore_unionobject.h"   // _Py_union_type_or
 
@@ -168,7 +168,7 @@ constevaluator_call(PyObject *self, PyObject *args, 
PyObject *kwargs)
         return NULL;
     }
     PyObject *value = ((constevaluatorobject *)self)->value;
-    if (format == 3) { // STRING
+    if (format == _Py_ANNOTATE_FORMAT_STRING) {
         PyUnicodeWriter *writer = PyUnicodeWriter_Create(5);  // cannot be <5
         if (writer == NULL) {
             return NULL;
diff --git a/Python/codegen.c b/Python/codegen.c
index dbf36cdc0b7908..a5e550cf8c947e 100644
--- a/Python/codegen.c
+++ b/Python/codegen.c
@@ -24,6 +24,7 @@
 #include "pycore_instruction_sequence.h" // _PyInstructionSequence_NewLabel()
 #include "pycore_intrinsics.h"
 #include "pycore_long.h"          // _PyLong_GetZero()
+#include "pycore_object.h"        // 
_Py_ANNOTATE_FORMAT_VALUE_WITH_FAKE_GLOBALS
 #include "pycore_pystate.h"       // _Py_GetConfig()
 #include "pycore_symtable.h"      // PySTEntryObject
 
@@ -672,14 +673,16 @@ codegen_setup_annotations_scope(compiler *c, location loc,
         codegen_enter_scope(c, name, COMPILE_SCOPE_ANNOTATIONS,
                             key, loc.lineno, NULL, &umd));
 
+    // if .format > VALUE_WITH_FAKE_GLOBALS: raise NotImplementedError
+    PyObject *value_with_fake_globals = 
PyLong_FromLong(_Py_ANNOTATE_FORMAT_VALUE_WITH_FAKE_GLOBALS);
     assert(!SYMTABLE_ENTRY(c)->ste_has_docstring);
-    // if .format != 1: 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]);
+    ADDOP_LOAD_CONST(c, loc, value_with_fake_globals);
+    ADDOP_I(c, loc, COMPARE_OP, (Py_GT << 5) | compare_masks[Py_GT]);
     NEW_JUMP_TARGET_LABEL(c, body);
     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);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to