summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/annotationlib.rst11
-rw-r--r--Include/internal/pycore_object.h7
-rw-r--r--Lib/annotationlib.py13
-rw-r--r--Lib/test/test_annotationlib.py36
-rw-r--r--Lib/test/test_type_annotations.py4
-rw-r--r--Lib/typing.py15
-rw-r--r--Objects/typevarobject.c4
-rw-r--r--Python/codegen.c9
8 files changed, 73 insertions, 26 deletions
diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst
index 3749045..dcaff3d 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 783d88c..34d835a 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 732fbfa..42f1f38 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -22,8 +22,9 @@ __all__ = [
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 2ca7058..20f74b4 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -42,11 +42,14 @@ class TestFormat(unittest.TestCase):
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 @@ class TestGetAnnotations(unittest.TestCase):
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 @@ class TestGetAnnotations(unittest.TestCase):
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 @@ class TestGetAnnotations(unittest.TestCase):
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 @@ class TestGetAnnotations(unittest.TestCase):
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 257b7fa..7d88f4c 100644
--- a/Lib/test/test_type_annotations.py
+++ b/Lib/test/test_type_annotations.py
@@ -316,7 +316,7 @@ class DeferredEvaluationTests(unittest.TestCase):
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 DeferredEvaluationTests(unittest.TestCase):
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 938e529..5f3aacd 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 @@ class _TypedDictMeta(type):
}
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 bacb858..4ed40aa 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 dbf36cd..a5e550c 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);