mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
gh-119180: Add VALUE_WITH_FAKE_GLOBALS format to annotationlib (#124415)
This commit is contained in:
parent
2b0e2b2893
commit
dcf629213b
8 changed files with 73 additions and 26 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue