From 985f8df0801d7cacbe0c7ab274a8e2e4aca6d50d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 20 May 2024 09:59:42 -0400 Subject: [PATCH 01/61] Add __annotate__ descriptors --- Include/cpython/funcobject.h | 1 + .../pycore_global_objects_fini_generated.h | 2 + Include/internal/pycore_global_strings.h | 2 + .../internal/pycore_runtime_init_generated.h | 2 + .../internal/pycore_unicodeobject_generated.h | 6 ++ Lib/test/test_type_annotations.py | 44 ++++++++ Objects/funcobject.c | 69 +++++++++++- Objects/moduleobject.c | 101 ++++++++++++++++-- Objects/typeobject.c | 93 +++++++++++++++- 9 files changed, 305 insertions(+), 15 deletions(-) diff --git a/Include/cpython/funcobject.h b/Include/cpython/funcobject.h index 5433ba48eefc69..598cd330bc9ca9 100644 --- a/Include/cpython/funcobject.h +++ b/Include/cpython/funcobject.h @@ -41,6 +41,7 @@ typedef struct { PyObject *func_weakreflist; /* List of weak references */ PyObject *func_module; /* The __module__ attribute, can be anything */ PyObject *func_annotations; /* Annotations, a dict or NULL */ + PyObject *func_annotate; /* Callable to fill the annotations dictionary */ PyObject *func_typeparams; /* Tuple of active type variables or NULL */ vectorcallfunc vectorcall; /* Version number for use by specializer. diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index ca7355b2b61aa7..158584b504e465 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -590,6 +590,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__all__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__and__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__anext__)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__annotate__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__annotations__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__args__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__asyncio_running_event_loop__)); @@ -989,6 +990,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hi)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hook)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hour)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(id)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ident)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(identity_hint)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ignore)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index fbb25285f0f282..769aa88b54c710 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -79,6 +79,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(__all__) STRUCT_FOR_ID(__and__) STRUCT_FOR_ID(__anext__) + STRUCT_FOR_ID(__annotate__) STRUCT_FOR_ID(__annotations__) STRUCT_FOR_ID(__args__) STRUCT_FOR_ID(__asyncio_running_event_loop__) @@ -478,6 +479,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(hi) STRUCT_FOR_ID(hook) STRUCT_FOR_ID(hour) + STRUCT_FOR_ID(id) STRUCT_FOR_ID(ident) STRUCT_FOR_ID(identity_hint) STRUCT_FOR_ID(ignore) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 508da40c53422d..065b18efd7922c 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -588,6 +588,7 @@ extern "C" { INIT_ID(__all__), \ INIT_ID(__and__), \ INIT_ID(__anext__), \ + INIT_ID(__annotate__), \ INIT_ID(__annotations__), \ INIT_ID(__args__), \ INIT_ID(__asyncio_running_event_loop__), \ @@ -987,6 +988,7 @@ extern "C" { INIT_ID(hi), \ INIT_ID(hook), \ INIT_ID(hour), \ + INIT_ID(id), \ INIT_ID(ident), \ INIT_ID(identity_hint), \ INIT_ID(ignore), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index cc2fc15ac5cabf..334dcce03aaf49 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -78,6 +78,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(__anext__); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(__annotate__); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(__annotations__); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1275,6 +1278,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(hour); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(id); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(ident); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 3dbb35afcb620f..55053df5710844 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -1,4 +1,5 @@ import textwrap +import types import unittest from test.support import run_code @@ -212,3 +213,46 @@ def test_match(self): case 0: x: int = 1 """) + + +class AnnotateTests(unittest.TestCase): + """See PEP 649.""" + def test_manual_annotate_function(self): + def f(): + pass + mod = types.ModuleType("mod") + class X: + pass + + for obj in (f, mod, X): + with self.subTest(obj=obj): + self.check_annotations(obj) + + def check_annotations(self, f): + self.assertEqual(f.__annotations__, {}) + self.assertIs(f.__annotate__, None) + + with self.assertRaises(TypeError): + f.__annotate__ = 42 + f.__annotate__ = lambda: 42 + with self.assertRaisesRegex(TypeError, r"takes 0 positional arguments but 1 was given"): + print(f.__annotations__) + + f.__annotate__ = lambda x: 42 + with self.assertRaisesRegex(TypeError, r"__annotate__ returned a non-dict"): + print(f.__annotations__) + + f.__annotate__ = lambda x: {"x": x} + self.assertEqual(f.__annotations__, {"x": 1}) + + # Setting annotate to None does not invalidate the cached __annotations__ + f.__annotate__ = None + self.assertEqual(f.__annotations__, {"x": 1}) + + # But setting it to a new callable does + f.__annotate__ = lambda x: {"y": x} + self.assertEqual(f.__annotations__, {"y": 1}) + + # Setting f.__annotations__ also clears __annotate__ + f.__annotations__ = {"z": 43} + self.assertIs(f.__annotate__, None) diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 8a30213888ef87..72d74516cc4f53 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -3,6 +3,7 @@ #include "Python.h" #include "pycore_ceval.h" // _PyEval_BuiltinsFromGlobals() +#include "pycore_long.h" // _PyLong_GetOne() #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_pyerrors.h" // _PyErr_Occurred() @@ -124,6 +125,7 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr) op->func_weakreflist = NULL; op->func_module = module; op->func_annotations = NULL; + op->func_annotate = NULL; op->func_typeparams = NULL; op->vectorcall = _PyFunction_Vectorcall; op->func_version = 0; @@ -202,6 +204,7 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname op->func_weakreflist = NULL; op->func_module = module; op->func_annotations = NULL; + op->func_annotate = NULL; op->func_typeparams = NULL; op->vectorcall = _PyFunction_Vectorcall; op->func_version = 0; @@ -512,7 +515,27 @@ static PyObject * func_get_annotation_dict(PyFunctionObject *op) { if (op->func_annotations == NULL) { - return NULL; + if (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate)) { + return NULL; + } + PyObject *one = _PyLong_GetOne(); + PyObject *ann_dict = _PyObject_CallOneArg(op->func_annotate, one); + if (ann_dict == NULL) { + return NULL; + } + if (op->func_annotations != NULL) { + Py_DECREF(ann_dict); + assert(PyDict_Check(op->func_annotations)); + return op->func_annotations; + } + if (!PyDict_Check(ann_dict)) { + PyErr_SetString(PyExc_TypeError, + "__annotate__ returned a non-dict"); + Py_DECREF(ann_dict); + return NULL; + } + Py_XSETREF(op->func_annotations, ann_dict); + return ann_dict; } if (PyTuple_CheckExact(op->func_annotations)) { PyObject *ann_tuple = op->func_annotations; @@ -565,7 +588,9 @@ PyFunction_SetAnnotations(PyObject *op, PyObject *annotations) "non-dict annotations"); return -1; } - Py_XSETREF(((PyFunctionObject *)op)->func_annotations, annotations); + PyFunctionObject *func = (PyFunctionObject *)op; + Py_XSETREF(func->func_annotations, annotations); + Py_CLEAR(func->func_annotate); return 0; } @@ -763,10 +788,44 @@ func_set_kwdefaults(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignor return 0; } +static PyObject * +func_get_annotate(PyFunctionObject *op, void *Py_UNUSED(ignored)) +{ + if (op->func_annotate == NULL) { + Py_RETURN_NONE; + } + return Py_NewRef(op->func_annotate); +} + +static int +func_set_annotate(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored)) +{ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, + "__annotate__ cannot be deleted"); + return -1; + } + if (Py_IsNone(value)) { + Py_XSETREF(op->func_annotate, value); + return 0; + } + else if (PyCallable_Check(value)) { + Py_XSETREF(op->func_annotate, Py_XNewRef(value)); + Py_CLEAR(op->func_annotations); + return 0; + } + else { + PyErr_SetString(PyExc_TypeError, + "__annotate__ must be set to a callable object or None"); + return -1; + } +} + static PyObject * func_get_annotations(PyFunctionObject *op, void *Py_UNUSED(ignored)) { - if (op->func_annotations == NULL) { + if (op->func_annotations == NULL && + (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate))) { op->func_annotations = PyDict_New(); if (op->func_annotations == NULL) return NULL; @@ -789,6 +848,7 @@ func_set_annotations(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(igno return -1; } Py_XSETREF(op->func_annotations, Py_XNewRef(value)); + Py_CLEAR(op->func_annotate); return 0; } @@ -836,6 +896,7 @@ static PyGetSetDef func_getsetlist[] = { (setter)func_set_kwdefaults}, {"__annotations__", (getter)func_get_annotations, (setter)func_set_annotations}, + {"__annotate__", (getter)func_get_annotate, (setter)func_set_annotate}, {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict}, {"__name__", (getter)func_get_name, (setter)func_set_name}, {"__qualname__", (getter)func_get_qualname, (setter)func_set_qualname}, @@ -972,6 +1033,7 @@ func_clear(PyFunctionObject *op) Py_CLEAR(op->func_dict); Py_CLEAR(op->func_closure); Py_CLEAR(op->func_annotations); + Py_CLEAR(op->func_annotate); Py_CLEAR(op->func_typeparams); // Don't Py_CLEAR(op->func_code), since code is always required // to be non-NULL. Similarly, name and qualname shouldn't be NULL. @@ -1028,6 +1090,7 @@ func_traverse(PyFunctionObject *f, visitproc visit, void *arg) Py_VISIT(f->func_dict); Py_VISIT(f->func_closure); Py_VISIT(f->func_annotations); + Py_VISIT(f->func_annotate); Py_VISIT(f->func_typeparams); Py_VISIT(f->func_qualname); return 0; diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 46995b948a28e7..3b378482be72f8 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -5,6 +5,7 @@ #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_fileutils.h" // _Py_wgetcwd #include "pycore_interp.h" // PyInterpreterState.importlib +#include "pycore_long.h" // _PyLong_GetOne() #include "pycore_modsupport.h" // _PyModule_CreateInitialized() #include "pycore_moduleobject.h" // _PyModule_GetDef() #include "pycore_object.h" // _PyType_AllocNoTrack @@ -1133,7 +1134,7 @@ static PyMethodDef module_methods[] = { }; static PyObject * -module_get_annotations(PyModuleObject *m, void *Py_UNUSED(ignored)) +module_get_dict(PyModuleObject *m) { PyObject *dict = PyObject_GetAttr((PyObject *)m, &_Py_ID(__dict__)); if (dict == NULL) { @@ -1144,10 +1145,93 @@ module_get_annotations(PyModuleObject *m, void *Py_UNUSED(ignored)) Py_DECREF(dict); return NULL; } + return dict; +} + +static PyObject * +module_get_annotate(PyModuleObject *m, void *Py_UNUSED(ignored)) +{ + PyObject *dict = module_get_dict(m); + if (dict == NULL) { + return NULL; + } + + PyObject *annotate; + if (PyDict_GetItemRef(dict, &_Py_ID(__annotate__), &annotate) == 0) { + annotate = Py_None; + int result = PyDict_SetItem(dict, &_Py_ID(__annotate__), annotate); + if (result == -1) { + Py_CLEAR(annotate); + } + } + Py_DECREF(dict); + return annotate; +} + +static int +module_set_annotate(PyModuleObject *m, PyObject *value, void *Py_UNUSED(ignored)) +{ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "cannot delete __annotate__ attribute"); + return -1; + } + PyObject *dict = module_get_dict(m); + if (dict == NULL) { + return -1; + } + + if (!Py_IsNone(value) && !PyCallable_Check(value)) { + PyErr_SetString(PyExc_TypeError, "__annotate__ must be callable or None"); + Py_DECREF(dict); + return -1; + } + + if (PyDict_SetItem(dict, &_Py_ID(__annotate__), value) == -1) { + Py_DECREF(dict); + return -1; + } + if (!Py_IsNone(value)) { + if (PyDict_Pop(dict, &_Py_ID(__annotations__), NULL) == -1) { + Py_DECREF(dict); + return -1; + } + } + Py_DECREF(dict); + return 0; +} + +static PyObject * +module_get_annotations(PyModuleObject *m, void *Py_UNUSED(ignored)) +{ + PyObject *dict = module_get_dict(m); + if (dict == NULL) { + return NULL; + } PyObject *annotations; if (PyDict_GetItemRef(dict, &_Py_ID(__annotations__), &annotations) == 0) { - annotations = PyDict_New(); + PyObject *annotate; + int annotate_result = PyDict_GetItemRef(dict, &_Py_ID(__annotate__), &annotate); + if (annotate_result < 0) { + Py_DECREF(dict); + return NULL; + } + if (annotate_result == 1 && PyCallable_Check(annotate)) { + PyObject *one = _PyLong_GetOne(); + annotations = _PyObject_CallOneArg(annotate, one); + if (annotations == NULL) { + return NULL; + } + if (!PyDict_Check(annotations)) { + PyErr_SetString(PyExc_TypeError, "__annotate__ returned a non-dict"); + Py_DECREF(annotations); + Py_DECREF(dict); + return NULL; + } + } + else { + annotations = PyDict_New(); + } if (annotations) { int result = PyDict_SetItem( dict, &_Py_ID(__annotations__), annotations); @@ -1164,14 +1248,10 @@ static int module_set_annotations(PyModuleObject *m, PyObject *value, void *Py_UNUSED(ignored)) { int ret = -1; - PyObject *dict = PyObject_GetAttr((PyObject *)m, &_Py_ID(__dict__)); + PyObject *dict = module_get_dict(m); if (dict == NULL) { return -1; } - if (!PyDict_Check(dict)) { - PyErr_Format(PyExc_TypeError, ".__dict__ is not a dictionary"); - goto exit; - } if (value != NULL) { /* set */ @@ -1188,8 +1268,12 @@ module_set_annotations(PyModuleObject *m, PyObject *value, void *Py_UNUSED(ignor ret = 0; } } + if (ret == 0) { + if (PyDict_Pop(dict, &_Py_ID(__annotate__), NULL) < 0) { + ret = -1; + } + } -exit: Py_DECREF(dict); return ret; } @@ -1197,6 +1281,7 @@ module_set_annotations(PyModuleObject *m, PyObject *value, void *Py_UNUSED(ignor static PyGetSetDef module_getsets[] = { {"__annotations__", (getter)module_get_annotations, (setter)module_set_annotations}, + {"__annotate__", (getter)module_get_annotate, (setter)module_set_annotate}, {NULL} }; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b7c3fcf47f23fc..82bd707c8b55cf 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7,7 +7,7 @@ #include "pycore_dict.h" // _PyDict_KeysSize() #include "pycore_frame.h" // _PyInterpreterFrame #include "pycore_lock.h" // _PySeqLock_* -#include "pycore_long.h" // _PyLong_IsNegative() +#include "pycore_long.h" // _PyLong_IsNegative(), _PyLong_GetOne() #include "pycore_memoryobject.h" // _PyMemoryView_FromBufferProc() #include "pycore_modsupport.h" // _PyArg_NoKwnames() #include "pycore_moduleobject.h" // _PyModule_GetDef() @@ -1674,6 +1674,68 @@ type_set_doc(PyTypeObject *type, PyObject *value, void *context) return PyDict_SetItem(dict, &_Py_ID(__doc__), value); } +static PyObject * +type_get_annotate(PyTypeObject *type, void *Py_UNUSED(ignored)) +{ + if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { + PyErr_Format(PyExc_AttributeError, "type object '%s' has no attribute '__annotate__'", type->tp_name); + return NULL; + } + + PyObject *annotate; + PyObject *dict = lookup_tp_dict(type); + if (PyDict_GetItemRef(dict, &_Py_ID(__annotate__), &annotate) < 0) { + return NULL; + } + if (annotate) { + descrgetfunc get = Py_TYPE(annotate)->tp_descr_get; + if (get) { + Py_SETREF(annotate, get(annotate, NULL, (PyObject *)type)); + } + } + else { + annotate = Py_None; + int result = PyDict_SetItem(dict, &_Py_ID(__annotate__), annotate); + if (result == 0) { + PyType_Modified(type); + } + } + return annotate; +} + +static int +type_set_annotate(PyTypeObject *type, PyObject *value, void *Py_UNUSED(ignored)) +{ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "cannot delete __annotate__ attribute"); + return -1; + } + if (_PyType_HasFeature(type, Py_TPFLAGS_IMMUTABLETYPE)) { + PyErr_Format(PyExc_TypeError, + "cannot set '__annotate__' attribute of immutable type '%s'", + type->tp_name); + return -1; + } + + if (!Py_IsNone(value) && !PyCallable_Check(value)) { + PyErr_SetString(PyExc_TypeError, "__annotate__ must be callable or None"); + return -1; + } + + PyObject *dict = lookup_tp_dict(type); + assert(PyDict_Check(dict)); + int result = PyDict_SetItem(dict, &_Py_ID(__annotate__), value); + if (result < 0) { + return -1; + } + if (!Py_IsNone(value)) { + if (PyDict_Pop(dict, &_Py_ID(__annotations__), NULL) == -1) { + return -1; + } + } + return 0; +} + static PyObject * type_get_annotations(PyTypeObject *type, void *context) { @@ -1694,7 +1756,25 @@ type_get_annotations(PyTypeObject *type, void *context) } } else { - annotations = PyDict_New(); + PyObject *annotate = type_get_annotate(type, NULL); + if (annotate == NULL) { + return NULL; + } + if (PyCallable_Check(annotate)) { + PyObject *one = _PyLong_GetOne(); + annotations = _PyObject_CallOneArg(annotate, one); + if (annotations == NULL) { + return NULL; + } + if (!PyDict_Check(annotations)) { + PyErr_SetString(PyExc_TypeError, "__annotate__ returned a non-dict"); + Py_DECREF(annotations); + return NULL; + } + } + else { + annotations = PyDict_New(); + } if (annotations) { int result = PyDict_SetItem( dict, &_Py_ID(__annotations__), annotations); @@ -1731,11 +1811,15 @@ type_set_annotations(PyTypeObject *type, PyObject *value, void *context) return -1; } } + PyType_Modified(type); if (result < 0) { return -1; } - - PyType_Modified(type); + else if (result == 0) { + if (PyDict_Pop(dict, &_Py_ID(__annotate__), NULL) < 0) { + return -1; + } + } return 0; } @@ -1811,6 +1895,7 @@ static PyGetSetDef type_getsets[] = { {"__doc__", (getter)type_get_doc, (setter)type_set_doc, NULL}, {"__text_signature__", (getter)type_get_text_signature, NULL, NULL}, {"__annotations__", (getter)type_get_annotations, (setter)type_set_annotations, NULL}, + {"__annotate__", (getter)type_get_annotate, (setter)type_set_annotate, NULL}, {"__type_params__", (getter)type_get_type_params, (setter)type_set_type_params, NULL}, {0} }; From c822ffab59966c02b5acb12c6991b778d03dfade Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 20 May 2024 10:09:30 -0400 Subject: [PATCH 02/61] fix refleaks --- Lib/test/test_type_annotations.py | 4 ++-- Objects/funcobject.c | 2 +- Objects/moduleobject.c | 4 ++++ Objects/typeobject.c | 3 +++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 55053df5710844..ef1569487de18c 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -217,7 +217,7 @@ def test_match(self): class AnnotateTests(unittest.TestCase): """See PEP 649.""" - def test_manual_annotate_function(self): + def test_manual_annotate(self): def f(): pass mod = types.ModuleType("mod") @@ -232,7 +232,7 @@ def check_annotations(self, f): self.assertEqual(f.__annotations__, {}) self.assertIs(f.__annotate__, None) - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "__annotate__ must be callable or None"): f.__annotate__ = 42 f.__annotate__ = lambda: 42 with self.assertRaisesRegex(TypeError, r"takes 0 positional arguments but 1 was given"): diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 72d74516cc4f53..d957f27a0a6949 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -816,7 +816,7 @@ func_set_annotate(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored } else { PyErr_SetString(PyExc_TypeError, - "__annotate__ must be set to a callable object or None"); + "__annotate__ must be callable or None"); return -1; } } diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 3b378482be72f8..8ec74a4702b898 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -1220,10 +1220,13 @@ module_get_annotations(PyModuleObject *m, void *Py_UNUSED(ignored)) PyObject *one = _PyLong_GetOne(); annotations = _PyObject_CallOneArg(annotate, one); if (annotations == NULL) { + Py_DECREF(annotate); + Py_DECREF(dict); return NULL; } if (!PyDict_Check(annotations)) { PyErr_SetString(PyExc_TypeError, "__annotate__ returned a non-dict"); + Py_DECREF(annotate); Py_DECREF(annotations); Py_DECREF(dict); return NULL; @@ -1232,6 +1235,7 @@ module_get_annotations(PyModuleObject *m, void *Py_UNUSED(ignored)) else { annotations = PyDict_New(); } + Py_XDECREF(annotate); if (annotations) { int result = PyDict_SetItem( dict, &_Py_ID(__annotations__), annotations); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 82bd707c8b55cf..14d879236d58d2 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1764,17 +1764,20 @@ type_get_annotations(PyTypeObject *type, void *context) PyObject *one = _PyLong_GetOne(); annotations = _PyObject_CallOneArg(annotate, one); if (annotations == NULL) { + Py_DECREF(annotate); return NULL; } if (!PyDict_Check(annotations)) { PyErr_SetString(PyExc_TypeError, "__annotate__ returned a non-dict"); Py_DECREF(annotations); + Py_DECREF(annotate); return NULL; } } else { annotations = PyDict_New(); } + Py_DECREF(annotate); if (annotations) { int result = PyDict_SetItem( dict, &_Py_ID(__annotations__), annotations); From e80095ed46df46cfb37ddb815832513caf6a0014 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 20 May 2024 10:10:57 -0400 Subject: [PATCH 03/61] blurb --- .../2024-05-20-10-10-51.gh-issue-119180.35xqpu.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-05-20-10-10-51.gh-issue-119180.35xqpu.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-20-10-10-51.gh-issue-119180.35xqpu.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-20-10-10-51.gh-issue-119180.35xqpu.rst new file mode 100644 index 00000000000000..5a88ce097274fb --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-20-10-10-51.gh-issue-119180.35xqpu.rst @@ -0,0 +1,2 @@ +Add an ``__annotate__`` attribute to functions, classes, and modules as part +of :pep:`649`. Patch by Jelle Zijlstra. From 90ff2c40e562d7e5018189bb011bd42e7919f210 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 20 May 2024 10:25:12 -0400 Subject: [PATCH 04/61] Fix some tests --- Lib/test/test_sys.py | 2 +- Lib/test/test_typing.py | 2 +- Lib/typing.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index ee3bd0092f9bf3..8fe1d77756866a 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1564,7 +1564,7 @@ def func(): check(x, size('3Pi2cP7P2ic??2P')) # function def func(): pass - check(func, size('15Pi')) + check(func, size('16Pi')) class c(): @staticmethod def foo(): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 64c4c497eb8934..dac55ceb9e99e0 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3723,7 +3723,7 @@ def meth(self): pass acceptable_extra_attrs = { '_is_protocol', '_is_runtime_protocol', '__parameters__', - '__init__', '__annotations__', '__subclasshook__', + '__init__', '__annotations__', '__subclasshook__', '__annotate__', } self.assertLessEqual(vars(NonP).keys(), vars(C).keys() | acceptable_extra_attrs) self.assertLessEqual( diff --git a/Lib/typing.py b/Lib/typing.py index 434574559e04fc..be49aa63464f05 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1889,6 +1889,7 @@ class _TypingEllipsis: '__init__', '__module__', '__new__', '__slots__', '__subclasshook__', '__weakref__', '__class_getitem__', '__match_args__', '__static_attributes__', '__firstlineno__', + '__annotate__', }) # These special attributes will be not collected as protocol members. From 026c0ff458de7ea60af2569560a9f4dacddc18ae Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 20 May 2024 10:26:17 -0400 Subject: [PATCH 05/61] regen globals --- Include/internal/pycore_global_objects_fini_generated.h | 1 - Include/internal/pycore_global_strings.h | 1 - Include/internal/pycore_runtime_init_generated.h | 1 - Include/internal/pycore_unicodeobject_generated.h | 3 --- 4 files changed, 6 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 158584b504e465..33133aaaf00893 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -990,7 +990,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hi)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hook)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hour)); - _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(id)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ident)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(identity_hint)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ignore)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 769aa88b54c710..f5ea7b9bd7d433 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -479,7 +479,6 @@ struct _Py_global_strings { STRUCT_FOR_ID(hi) STRUCT_FOR_ID(hook) STRUCT_FOR_ID(hour) - STRUCT_FOR_ID(id) STRUCT_FOR_ID(ident) STRUCT_FOR_ID(identity_hint) STRUCT_FOR_ID(ignore) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 065b18efd7922c..c73408d6315312 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -988,7 +988,6 @@ extern "C" { INIT_ID(hi), \ INIT_ID(hook), \ INIT_ID(hour), \ - INIT_ID(id), \ INIT_ID(ident), \ INIT_ID(identity_hint), \ INIT_ID(ignore), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 334dcce03aaf49..d84c45a6b57887 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1278,9 +1278,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(hour); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); - string = &_Py_ID(id); - assert(_PyUnicode_CheckConsistency(string, 1)); - _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(ident); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); From 7968744f50c48c9e5afd872d37ee0fc70d04e473 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 20 May 2024 08:07:03 -0400 Subject: [PATCH 06/61] Some initial work --- Include/cpython/code.h | 2 +- Include/internal/pycore_symtable.h | 1 + Python/future.c | 4 ++- Python/symtable.c | 53 +++++++++++++++++++++++++++++- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/Include/cpython/code.h b/Include/cpython/code.h index ef8f9304ccab56..d64f79bceaa6c7 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -219,7 +219,7 @@ struct PyCodeObject _PyCode_DEF(1); #define CO_FUTURE_GENERATOR_STOP 0x800000 #define CO_FUTURE_ANNOTATIONS 0x1000000 -#define CO_NO_MONITORING_EVENTS 0x2000000 +#define CO_NO_MONITORING_EVENTS 0x4000000 /* This should be defined if a future statement modifies the syntax. For example, when a keyword is added. diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 16e89f80d9d0c8..8a5f56668c17a6 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -109,6 +109,7 @@ typedef struct _symtable_entry { int ste_end_col_offset; /* end offset of first line of block */ int ste_opt_lineno; /* lineno of last exec or import * */ int ste_opt_col_offset; /* offset of last exec or import * */ + struct _symtable_entry *ste_annotation_block; /* symbol table entry for this entry's annotations */ struct symtable *ste_table; } PySTEntryObject; diff --git a/Python/future.c b/Python/future.c index 8d94d515605dcd..9e528bf121346a 100644 --- a/Python/future.c +++ b/Python/future.c @@ -37,7 +37,9 @@ future_check_features(_PyFutureFeatures *ff, stmt_ty s, PyObject *filename) } else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) { continue; } else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) { - ff->ff_features |= CO_FUTURE_ANNOTATIONS; + // For now, we ignore this future. We may make it stringify again + // in the future. + // ff->ff_features |= CO_FUTURE_ANNOTATIONS; } else if (strcmp(feature, "braces") == 0) { PyErr_SetString(PyExc_SyntaxError, "not a chance"); diff --git a/Python/symtable.c b/Python/symtable.c index 2ec21a2d376da2..dcfe42305e3214 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -131,6 +131,7 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block, ste->ste_can_see_class_scope = 0; ste->ste_comp_iter_expr = 0; ste->ste_needs_classdict = 0; + ste->ste_annotation_block = NULL; ste->ste_symbols = PyDict_New(); ste->ste_varnames = PyList_New(0); @@ -166,6 +167,7 @@ ste_dealloc(PySTEntryObject *ste) Py_XDECREF(ste->ste_varnames); Py_XDECREF(ste->ste_children); Py_XDECREF(ste->ste_directives); + Py_XDECREF(ste->ste_annotation_block); PyObject_Free(ste); } @@ -1360,6 +1362,35 @@ symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block, return 1; } +static int +symtable_reenter_block(struct symtable *st, PySTEntryObject* ste) +{ + PySTEntryObject *prev = NULL; + + if (PyList_Append(st->st_stack, (PyObject *)ste) < 0) { + Py_DECREF(ste); + return 0; + } + prev = st->st_cur; + /* bpo-37757: For now, disallow *all* assignment expressions in the + * outermost iterator expression of a comprehension, even those inside + * a nested comprehension or a lambda expression. + */ + if (prev) { + ste->ste_comp_iter_expr = prev->ste_comp_iter_expr; + } + /* The entry is owned by the stack. Borrow it for st_cur. */ + Py_DECREF(ste); + st->st_cur = ste; + + if (prev) { + if (PyList_Append(prev->ste_children, (PyObject *)ste) < 0) { + return 0; + } + } + return 1; +} + static long symtable_lookup_entry(struct symtable *st, PySTEntryObject *ste, PyObject *name) { @@ -1666,6 +1697,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) if (!symtable_exit_block(st)) VISIT_QUIT(st, 0); } + Py_CLEAR(st->st_cur->ste_annotation_block); break; case ClassDef_kind: { PyObject *tmp; @@ -1981,6 +2013,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) if (!symtable_exit_block(st)) VISIT_QUIT(st, 0); } + Py_CLEAR(st->st_cur->ste_annotation_block); break; case AsyncWith_kind: VISIT_SEQ(st, withitem, s->v.AsyncWith.items); @@ -2435,8 +2468,26 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation) annotation->end_col_offset)) { VISIT_QUIT(st, 0); } + else { + if (st->st_cur->ste_annotation_block == NULL) { + struct _symtable_entry *st_parent = st->st_cur; + if (!symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, + (void *)annotation, annotation->lineno, + annotation->col_offset, annotation->end_lineno, + annotation->end_col_offset)) { + VISIT_QUIT(st, 0); + } + st_parent->ste_annotation_block = + (struct _symtable_entry *)Py_NewRef(st->st_cur); + } + else { + if (!symtable_reenter_block(st, st->st_cur->ste_annotation_block)) { + VISIT_QUIT(st, 0); + } + } + } VISIT(st, expr, annotation); - if (future_annotations && !symtable_exit_block(st)) { + if (!symtable_exit_block(st)) { VISIT_QUIT(st, 0); } return 1; From 4e54197422859fc8ad229cea917ee9c48c1d903f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 20 May 2024 11:34:16 -0400 Subject: [PATCH 07/61] Add bytecode for adding annotate --- Include/internal/pycore_opcode_utils.h | 1 + Python/bytecodes.c | 5 +++++ Python/executor_cases.c.h | 5 +++++ Python/generated_cases.c.h | 5 +++++ 4 files changed, 16 insertions(+) diff --git a/Include/internal/pycore_opcode_utils.h b/Include/internal/pycore_opcode_utils.h index 208bfb2f75308b..1be0820dbac5e0 100644 --- a/Include/internal/pycore_opcode_utils.h +++ b/Include/internal/pycore_opcode_utils.h @@ -57,6 +57,7 @@ extern "C" { #define MAKE_FUNCTION_KWDEFAULTS 0x02 #define MAKE_FUNCTION_ANNOTATIONS 0x04 #define MAKE_FUNCTION_CLOSURE 0x08 +#define MAKE_FUNCTION_ANNOTATE 0x10 /* Values used in the oparg for RESUME */ #define RESUME_AT_FUNC_START 0 diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 55eda9711dea1f..eb1acbdc13048e 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3957,6 +3957,11 @@ dummy_func( assert(func_obj->func_defaults == NULL); func_obj->func_defaults = attr; break; + case MAKE_FUNCTION_ANNOTATE: + assert(PyCallable_Check(attr)); + assert(func_obj->func_annotate == NULL); + func_obj->func_annotate = attr; + break; default: Py_UNREACHABLE(); } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 347a1e677a0832..a7b9ac69c52bf0 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -4026,6 +4026,11 @@ assert(func_obj->func_defaults == NULL); func_obj->func_defaults = attr; break; + case MAKE_FUNCTION_ANNOTATE: + assert(PyCallable_Check(attr)); + assert(func_obj->func_annotate == NULL); + func_obj->func_annotate = attr; + break; default: Py_UNREACHABLE(); } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 8b8112209cc78a..d96825b6fdeff0 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -5417,6 +5417,11 @@ assert(func_obj->func_defaults == NULL); func_obj->func_defaults = attr; break; + case MAKE_FUNCTION_ANNOTATE: + assert(PyCallable_Check(attr)); + assert(func_obj->func_annotate == NULL); + func_obj->func_annotate = attr; + break; default: Py_UNREACHABLE(); } From 4469b32bee8226fa04061cea9d1d9800fa534f68 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 20 May 2024 13:38:41 -0400 Subject: [PATCH 08/61] compiler changes --- Python/compile.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 79f3baadca6b4a..4037ec4cdec093 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1828,6 +1828,9 @@ compiler_make_closure(struct compiler *c, location loc, if (flags & MAKE_FUNCTION_DEFAULTS) { ADDOP_I(c, loc, SET_FUNCTION_ATTRIBUTE, MAKE_FUNCTION_DEFAULTS); } + if (flags & MAKE_FUNCTION_ANNOTATE) { + ADDOP_I(c, loc, SET_FUNCTION_ATTRIBUTE, MAKE_FUNCTION_ANNOTATE); + } return SUCCESS; } @@ -1982,9 +1985,10 @@ compiler_visit_annotations(struct compiler *c, location loc, /* Push arg annotation names and values. The expressions are evaluated out-of-order wrt the source code. - Return -1 on error, 0 if no annotations pushed, 1 if a annotations is pushed. + Return -1 on error, or a combination of flags to add to the function. */ Py_ssize_t annotations_len = 0; + int future_annotations = c->c_future.ff_features & CO_FUTURE_ANNOTATIONS; RETURN_IF_ERROR( compiler_visit_argannotations(c, args->args, &annotations_len, loc)); @@ -2012,7 +2016,7 @@ compiler_visit_annotations(struct compiler *c, location loc, if (annotations_len) { ADDOP_I(c, loc, BUILD_TUPLE, annotations_len); - return 1; + return MAKE_FUNCTION_ANNOTATIONS; } return 0; @@ -2413,9 +2417,7 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async) } return ERROR; } - if (annotations > 0) { - funcflags |= MAKE_FUNCTION_ANNOTATIONS; - } + funcflags |= annotations; if (compiler_function_body(c, s, is_async, funcflags, firstlineno) < 0) { if (is_generic) { From 47d672ee233658bda051784b0655cb5738b1502d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 10:34:21 -0400 Subject: [PATCH 09/61] Functions work --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + Include/internal/pycore_symtable.h | 7 +- Python/compile.c | 104 ++++++++--- Python/symtable.c | 176 ++++++++++-------- 6 files changed, 184 insertions(+), 106 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 33133aaaf00893..dc4fd3282b9e13 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -559,6 +559,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(dot)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(dot_locals)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(empty)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(format)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(generic_base)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(json_decoder)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(kwdefaults)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index f5ea7b9bd7d433..ab005022dd5ab2 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -45,6 +45,7 @@ struct _Py_global_strings { STRUCT_FOR_STR(dot, ".") STRUCT_FOR_STR(dot_locals, ".") STRUCT_FOR_STR(empty, "") + STRUCT_FOR_STR(format, ".format") STRUCT_FOR_STR(generic_base, ".generic_base") STRUCT_FOR_STR(json_decoder, "json.decoder") STRUCT_FOR_STR(kwdefaults, ".kwdefaults") diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index c73408d6315312..dda7b1329deb2a 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -554,6 +554,7 @@ extern "C" { INIT_STR(dot, "."), \ INIT_STR(dot_locals, "."), \ INIT_STR(empty, ""), \ + INIT_STR(format, ".format"), \ INIT_STR(generic_base, ".generic_base"), \ INIT_STR(json_decoder, "json.decoder"), \ INIT_STR(kwdefaults, ".kwdefaults"), \ diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 8a5f56668c17a6..f109a7229c35df 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -12,8 +12,9 @@ struct _mod; // Type defined in pycore_ast.h typedef enum _block_type { FunctionBlock, ClassBlock, ModuleBlock, - // Used for annotations if 'from __future__ import annotations' is active. - // Annotation blocks cannot bind names and are not evaluated. + // Used for annotations. If 'from __future__ import annotations' is active, + // annotation blocks cannot bind names and are not evaluated. Otherwise, they + // are lazily evaluated (see PEP 649). AnnotationBlock, // Used for generics and type aliases. These work mostly like functions // (see PEP 695 for details). The three different blocks function identically; @@ -88,6 +89,7 @@ typedef struct _symtable_entry { including free refs to globals */ unsigned ste_generator : 1; /* true if namespace is a generator */ unsigned ste_coroutine : 1; /* true if namespace is a coroutine */ + unsigned ste_annotations_used : 1; /* true if there are any annotations in this scope */ _Py_comprehension_ty ste_comprehension; /* Kind of comprehension (if any) */ unsigned ste_varargs : 1; /* true if block has varargs */ unsigned ste_varkeywords : 1; /* true if block has varkeywords */ @@ -126,6 +128,7 @@ extern struct symtable* _PySymtable_Build( PyObject *filename, _PyFutureFeatures *future); extern PySTEntryObject* _PySymtable_Lookup(struct symtable *, void *); +extern int _PySymtable_LookupOptional(struct symtable *, void *, PySTEntryObject **); extern void _PySymtable_Free(struct symtable *); diff --git a/Python/compile.c b/Python/compile.c index 4037ec4cdec093..c76c9b146c9c34 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -132,7 +132,7 @@ enum { COMPILER_SCOPE_ASYNC_FUNCTION, COMPILER_SCOPE_LAMBDA, COMPILER_SCOPE_COMPREHENSION, - COMPILER_SCOPE_TYPEPARAMS, + COMPILER_SCOPE_ANNOTATIONS, }; @@ -623,8 +623,8 @@ compiler_set_qualname(struct compiler *c) capsule = PyList_GET_ITEM(c->c_stack, stack_size - 1); parent = (struct compiler_unit *)PyCapsule_GetPointer(capsule, CAPSULE_NAME); assert(parent); - if (parent->u_scope_type == COMPILER_SCOPE_TYPEPARAMS) { - /* The parent is a type parameter scope, so we need to + if (parent->u_scope_type == COMPILER_SCOPE_ANNOTATIONS) { + /* The parent is an annotation scope, so we need to look at the grandparent. */ if (stack_size == 2) { // If we're immediately within the module, we can skip @@ -1822,15 +1822,15 @@ compiler_make_closure(struct compiler *c, location loc, if (flags & MAKE_FUNCTION_ANNOTATIONS) { ADDOP_I(c, loc, SET_FUNCTION_ATTRIBUTE, MAKE_FUNCTION_ANNOTATIONS); } + if (flags & MAKE_FUNCTION_ANNOTATE) { + ADDOP_I(c, loc, SET_FUNCTION_ATTRIBUTE, MAKE_FUNCTION_ANNOTATE); + } if (flags & MAKE_FUNCTION_KWDEFAULTS) { ADDOP_I(c, loc, SET_FUNCTION_ATTRIBUTE, MAKE_FUNCTION_KWDEFAULTS); } if (flags & MAKE_FUNCTION_DEFAULTS) { ADDOP_I(c, loc, SET_FUNCTION_ATTRIBUTE, MAKE_FUNCTION_DEFAULTS); } - if (flags & MAKE_FUNCTION_ANNOTATE) { - ADDOP_I(c, loc, SET_FUNCTION_ATTRIBUTE, MAKE_FUNCTION_ANNOTATE); - } return SUCCESS; } @@ -1956,7 +1956,7 @@ compiler_visit_argannotation(struct compiler *c, identifier id, VISIT(c, expr, annotation); } } - *annotations_len += 2; + *annotations_len += 1; return SUCCESS; } @@ -1990,6 +1990,32 @@ compiler_visit_annotations(struct compiler *c, location loc, Py_ssize_t annotations_len = 0; int future_annotations = c->c_future.ff_features & CO_FUTURE_ANNOTATIONS; + PySTEntryObject *ste; + int result = _PySymtable_LookupOptional(c->c_st, args, &ste); + if (result == -1) { + return ERROR; + } + assert(ste != NULL); + + if (!future_annotations && ste->ste_annotations_used) { + PyObject *annotations_name = PyUnicode_FromFormat("", ste->ste_name); + if (!annotations_name) { + return ERROR; + } + if (compiler_enter_scope(c, annotations_name, COMPILER_SCOPE_ANNOTATIONS, + (void *)args, loc.lineno) == -1) { + Py_DECREF(annotations_name); + return ERROR; + } + Py_DECREF(annotations_name); + c->u->u_metadata.u_posonlyargcount = 1; + _Py_DECLARE_STR(format, ".format"); + // RETURN_IF_ERROR(compiler_nameop(c, loc, &_Py_STR(format), Load)); + // ADDOP(c, loc, POP_TOP); + // ADDOP_LOAD_CONST(c, loc, _PyLong_GetOne()); + // TODO: emit "if .format != 1: raise NotImplementedError()" + } + RETURN_IF_ERROR( compiler_visit_argannotations(c, args->args, &annotations_len, loc)); @@ -2014,10 +2040,28 @@ compiler_visit_annotations(struct compiler *c, location loc, RETURN_IF_ERROR( compiler_visit_argannotation(c, &_Py_ID(return), returns, &annotations_len, loc)); - if (annotations_len) { - ADDOP_I(c, loc, BUILD_TUPLE, annotations_len); + if (future_annotations) { + ADDOP_I(c, loc, BUILD_TUPLE, annotations_len * 2); return MAKE_FUNCTION_ANNOTATIONS; } + else { + assert(ste != NULL); + if (ste->ste_annotations_used) { + ADDOP_I(c, loc, BUILD_MAP, annotations_len); + ADDOP_IN_SCOPE(c, loc, RETURN_VALUE); + PyCodeObject *co = optimize_and_assemble(c, 1); + compiler_exit_scope(c); + if (co == NULL) { + return ERROR; + } + if (compiler_make_closure(c, loc, co, 0) < 0) { + Py_DECREF(co); + return ERROR; + } + Py_DECREF(co); + return MAKE_FUNCTION_ANNOTATE; + } + } return 0; } @@ -2125,7 +2169,7 @@ compiler_type_param_bound_or_default(struct compiler *c, expr_ty e, identifier name, void *key, bool allow_starred) { - if (compiler_enter_scope(c, name, COMPILER_SCOPE_TYPEPARAMS, + if (compiler_enter_scope(c, name, COMPILER_SCOPE_ANNOTATIONS, key, e->lineno) == -1) { return ERROR; } @@ -2398,7 +2442,7 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async) if (!type_params_name) { return ERROR; } - if (compiler_enter_scope(c, type_params_name, COMPILER_SCOPE_TYPEPARAMS, + if (compiler_enter_scope(c, type_params_name, COMPILER_SCOPE_ANNOTATIONS, (void *)type_params, firstlineno) == -1) { Py_DECREF(type_params_name); return ERROR; @@ -2633,7 +2677,7 @@ compiler_class(struct compiler *c, stmt_ty s) if (!type_params_name) { return ERROR; } - if (compiler_enter_scope(c, type_params_name, COMPILER_SCOPE_TYPEPARAMS, + if (compiler_enter_scope(c, type_params_name, COMPILER_SCOPE_ANNOTATIONS, (void *)type_params, firstlineno) == -1) { Py_DECREF(type_params_name); return ERROR; @@ -2752,7 +2796,7 @@ compiler_typealias(struct compiler *c, stmt_ty s) if (!type_params_name) { return ERROR; } - if (compiler_enter_scope(c, type_params_name, COMPILER_SCOPE_TYPEPARAMS, + if (compiler_enter_scope(c, type_params_name, COMPILER_SCOPE_ANNOTATIONS, (void *)type_params, loc.lineno) == -1) { Py_DECREF(type_params_name); return ERROR; @@ -6504,20 +6548,20 @@ compiler_annassign(struct compiler *c, stmt_ty s) return ERROR; } /* If we have a simple name in a module or class, store annotation. */ - if (s->v.AnnAssign.simple && - (c->u->u_scope_type == COMPILER_SCOPE_MODULE || - c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { - if (c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) { - VISIT(c, annexpr, s->v.AnnAssign.annotation) - } - else { - VISIT(c, expr, s->v.AnnAssign.annotation); - } - ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); - mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id); - ADDOP_LOAD_CONST_NEW(c, loc, mangled); - ADDOP(c, loc, STORE_SUBSCR); - } + // if (s->v.AnnAssign.simple && + // (c->u->u_scope_type == COMPILER_SCOPE_MODULE || + // c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { + // if (c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) { + // VISIT(c, annexpr, s->v.AnnAssign.annotation) + // } + // else { + // VISIT(c, expr, s->v.AnnAssign.annotation); + // } + // ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); + // mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id); + // ADDOP_LOAD_CONST_NEW(c, loc, mangled); + // ADDOP(c, loc, STORE_SUBSCR); + // } break; case Attribute_kind: if (forbidden_name(c, loc, targ->v.Attribute.attr, Store)) { @@ -6542,9 +6586,9 @@ compiler_annassign(struct compiler *c, stmt_ty s) return ERROR; } /* Annotation is evaluated last. */ - if (!s->v.AnnAssign.simple && check_annotation(c, s) < 0) { - return ERROR; - } + // if (!s->v.AnnAssign.simple && check_annotation(c, s) < 0) { + // return ERROR; + // } return SUCCESS; } diff --git a/Python/symtable.c b/Python/symtable.c index dcfe42305e3214..438751ac1aec8d 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -245,10 +245,12 @@ static int symtable_visit_alias(struct symtable *st, alias_ty); static int symtable_visit_comprehension(struct symtable *st, comprehension_ty); static int symtable_visit_keyword(struct symtable *st, keyword_ty); static int symtable_visit_params(struct symtable *st, asdl_arg_seq *args); -static int symtable_visit_annotation(struct symtable *st, expr_ty annotation); +static int symtable_visit_annotation(struct symtable *st, expr_ty annotation, + struct _symtable_entry *parent_ste, void *key); static int symtable_visit_argannotations(struct symtable *st, asdl_arg_seq *args); static int symtable_implicit_arg(struct symtable *st, int pos); -static int symtable_visit_annotations(struct symtable *st, stmt_ty, arguments_ty, expr_ty); +static int symtable_visit_annotations(struct symtable *st, stmt_ty, arguments_ty, expr_ty, + struct _symtable_entry *parent_ste); static int symtable_visit_withitem(struct symtable *st, withitem_ty item); static int symtable_visit_match_case(struct symtable *st, match_case_ty m); static int symtable_visit_pattern(struct symtable *st, pattern_ty s); @@ -504,6 +506,21 @@ _PySymtable_Lookup(struct symtable *st, void *key) return (PySTEntryObject *)v; } +int +_PySymtable_LookupOptional(struct symtable *st, void *key, + PySTEntryObject **out) +{ + PyObject *k = PyLong_FromVoidPtr(key); + if (k == NULL) { + *out = NULL; + return -1; + } + int result = PyDict_GetItemRef(st->st_blocks, k, (PyObject **)out); + Py_DECREF(k); + assert(*out == NULL || PySTEntry_Check(*out)); + return result; +} + long _PyST_GetSymbol(PySTEntryObject *ste, PyObject *name) { @@ -1319,20 +1336,12 @@ symtable_exit_block(struct symtable *st) } static int -symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block, - void *ast, int lineno, int col_offset, - int end_lineno, int end_col_offset) +symtable_enter_existing_block(struct symtable *st, PySTEntryObject* ste) { - PySTEntryObject *prev = NULL, *ste; - - ste = ste_new(st, name, block, ast, lineno, col_offset, end_lineno, end_col_offset); - if (ste == NULL) - return 0; if (PyList_Append(st->st_stack, (PyObject *)ste) < 0) { - Py_DECREF(ste); return 0; } - prev = st->st_cur; + PySTEntryObject *prev = st->st_cur; /* bpo-37757: For now, disallow *all* assignment expressions in the * outermost iterator expression of a comprehension, even those inside * a nested comprehension or a lambda expression. @@ -1341,17 +1350,16 @@ symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block, ste->ste_comp_iter_expr = prev->ste_comp_iter_expr; } /* The entry is owned by the stack. Borrow it for st_cur. */ - Py_DECREF(ste); st->st_cur = ste; /* Annotation blocks shouldn't have any affect on the symbol table since in * the compilation stage, they will all be transformed to strings. They are * only created if future 'annotations' feature is activated. */ - if (block == AnnotationBlock) { + if (st->st_future->ff_features & CO_FUTURE_ANNOTATIONS && ste->ste_type == AnnotationBlock) { return 1; } - if (block == ModuleBlock) + if (ste->ste_type == ModuleBlock) st->st_global = st->st_cur->ste_symbols; if (prev) { @@ -1363,32 +1371,17 @@ symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block, } static int -symtable_reenter_block(struct symtable *st, PySTEntryObject* ste) +symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block, + void *ast, int lineno, int col_offset, + int end_lineno, int end_col_offset) { - PySTEntryObject *prev = NULL; - - if (PyList_Append(st->st_stack, (PyObject *)ste) < 0) { - Py_DECREF(ste); + PySTEntryObject *ste = ste_new(st, name, block, ast, + lineno, col_offset, end_lineno, end_col_offset); + if (ste == NULL) return 0; - } - prev = st->st_cur; - /* bpo-37757: For now, disallow *all* assignment expressions in the - * outermost iterator expression of a comprehension, even those inside - * a nested comprehension or a lambda expression. - */ - if (prev) { - ste->ste_comp_iter_expr = prev->ste_comp_iter_expr; - } - /* The entry is owned by the stack. Borrow it for st_cur. */ + int result = symtable_enter_existing_block(st, ste); Py_DECREF(ste); - st->st_cur = ste; - - if (prev) { - if (PyList_Append(prev->ste_children, (PyObject *)ste) < 0) { - return 0; - } - } - return 1; + return result; } static long @@ -1660,7 +1653,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) VISIT_QUIT(st, 0); } switch (s->kind) { - case FunctionDef_kind: + case FunctionDef_kind: { if (!symtable_add_def(st, s->v.FunctionDef.name, DEF_LOCAL, LOCATION(s))) VISIT_QUIT(st, 0); if (s->v.FunctionDef.args->defaults) @@ -1682,13 +1675,20 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) } VISIT_SEQ(st, type_param, s->v.FunctionDef.type_params); } + PySTEntryObject *new_ste = ste_new(st, s->v.FunctionDef.name, FunctionBlock, (void *)s, + LOCATION(s)); + if (!new_ste) { + VISIT_QUIT(st, 0); + } + if (!symtable_visit_annotations(st, s, s->v.FunctionDef.args, - s->v.FunctionDef.returns)) + s->v.FunctionDef.returns, new_ste)) VISIT_QUIT(st, 0); - if (!symtable_enter_block(st, s->v.FunctionDef.name, - FunctionBlock, (void *)s, - LOCATION(s))) + if (!symtable_enter_existing_block(st, new_ste)) { + Py_DECREF(new_ste); VISIT_QUIT(st, 0); + } + Py_DECREF(new_ste); VISIT(st, arguments, s->v.FunctionDef.args); VISIT_SEQ(st, stmt, s->v.FunctionDef.body); if (!symtable_exit_block(st)) @@ -1697,8 +1697,8 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) if (!symtable_exit_block(st)) VISIT_QUIT(st, 0); } - Py_CLEAR(st->st_cur->ste_annotation_block); break; + } case ClassDef_kind: { PyObject *tmp; if (!symtable_add_def(st, s->v.ClassDef.name, DEF_LOCAL, LOCATION(s))) @@ -1823,7 +1823,8 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) else { VISIT(st, expr, s->v.AnnAssign.target); } - if (!symtable_visit_annotation(st, s->v.AnnAssign.annotation)) { + if (!symtable_visit_annotation(st, s->v.AnnAssign.annotation, st->st_cur, + (void *)((uintptr_t)st->st_cur->ste_id + 1))) { VISIT_QUIT(st, 0); } @@ -1973,7 +1974,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) VISIT_SEQ(st, withitem, s->v.With.items); VISIT_SEQ(st, stmt, s->v.With.body); break; - case AsyncFunctionDef_kind: + case AsyncFunctionDef_kind: { if (!symtable_add_def(st, s->v.AsyncFunctionDef.name, DEF_LOCAL, LOCATION(s))) VISIT_QUIT(st, 0); if (s->v.AsyncFunctionDef.args->defaults) @@ -1996,14 +1997,21 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) } VISIT_SEQ(st, type_param, s->v.AsyncFunctionDef.type_params); } + PySTEntryObject *new_ste = ste_new(st, s->v.FunctionDef.name, FunctionBlock, (void *)s, + LOCATION(s)); + if (!new_ste) { + VISIT_QUIT(st, 0); + } + if (!symtable_visit_annotations(st, s, s->v.AsyncFunctionDef.args, - s->v.AsyncFunctionDef.returns)) + s->v.AsyncFunctionDef.returns, new_ste)) VISIT_QUIT(st, 0); - if (!symtable_enter_block(st, s->v.AsyncFunctionDef.name, - FunctionBlock, (void *)s, - s->lineno, s->col_offset, - s->end_lineno, s->end_col_offset)) + if (!symtable_enter_existing_block(st, new_ste)) { + Py_DECREF(new_ste); VISIT_QUIT(st, 0); + } + Py_DECREF(new_ste); + st->st_cur->ste_coroutine = 1; VISIT(st, arguments, s->v.AsyncFunctionDef.args); VISIT_SEQ(st, stmt, s->v.AsyncFunctionDef.body); @@ -2013,8 +2021,8 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) if (!symtable_exit_block(st)) VISIT_QUIT(st, 0); } - Py_CLEAR(st->st_cur->ste_annotation_block); break; + } case AsyncWith_kind: VISIT_SEQ(st, withitem, s->v.AsyncWith.items); VISIT_SEQ(st, stmt, s->v.AsyncWith.body); @@ -2458,30 +2466,37 @@ symtable_visit_params(struct symtable *st, asdl_arg_seq *args) } static int -symtable_visit_annotation(struct symtable *st, expr_ty annotation) +symtable_visit_annotation(struct symtable *st, expr_ty annotation, + struct _symtable_entry *parent_ste, void *key) { int future_annotations = st->st_future->ff_features & CO_FUTURE_ANNOTATIONS; if (future_annotations && !symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, - (void *)annotation, annotation->lineno, - annotation->col_offset, annotation->end_lineno, - annotation->end_col_offset)) { + key, LOCATION(annotation))) { VISIT_QUIT(st, 0); } else { + printf("enter block %p\n", key); if (st->st_cur->ste_annotation_block == NULL) { - struct _symtable_entry *st_parent = st->st_cur; if (!symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, - (void *)annotation, annotation->lineno, - annotation->col_offset, annotation->end_lineno, - annotation->end_col_offset)) { + key, LOCATION(annotation))) { VISIT_QUIT(st, 0); } - st_parent->ste_annotation_block = + parent_ste->ste_annotation_block = (struct _symtable_entry *)Py_NewRef(st->st_cur); + _Py_DECLARE_STR(format, ".format"); + // We need to insert code that reads this "parameter" to the function. + if (!symtable_add_def(st, &_Py_STR(format), DEF_PARAM, + LOCATION(annotation))) { + return 0; + } + if (!symtable_add_def(st, &_Py_STR(format), USE, + LOCATION(annotation))) { + return 0; + } } else { - if (!symtable_reenter_block(st, st->st_cur->ste_annotation_block)) { + if (!symtable_enter_existing_block(st, st->st_cur->ste_annotation_block)) { VISIT_QUIT(st, 0); } } @@ -2503,37 +2518,50 @@ symtable_visit_argannotations(struct symtable *st, asdl_arg_seq *args) for (i = 0; i < asdl_seq_LEN(args); i++) { arg_ty arg = (arg_ty)asdl_seq_GET(args, i); - if (arg->annotation) + if (arg->annotation) { + st->st_cur->ste_annotations_used = 1; VISIT(st, expr, arg->annotation); + } } return 1; } static int -symtable_visit_annotations(struct symtable *st, stmt_ty o, arguments_ty a, expr_ty returns) +symtable_visit_annotations(struct symtable *st, stmt_ty o, arguments_ty a, expr_ty returns, + struct _symtable_entry *function_ste) { - int future_annotations = st->st_future->ff_features & CO_FUTURE_ANNOTATIONS; - if (future_annotations && - !symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, - (void *)o, o->lineno, o->col_offset, o->end_lineno, - o->end_col_offset)) { + if (!symtable_enter_block(st, function_ste->ste_name, AnnotationBlock, + (void *)a, LOCATION(o))) { VISIT_QUIT(st, 0); } + _Py_DECLARE_STR(format, ".format"); + // We need to insert code that reads this "parameter" to the function. + if (!symtable_add_def(st, &_Py_STR(format), DEF_PARAM, LOCATION(o))) { + return 0; + } + if (!symtable_add_def(st, &_Py_STR(format), USE, LOCATION(o))) { + return 0; + } if (a->posonlyargs && !symtable_visit_argannotations(st, a->posonlyargs)) return 0; if (a->args && !symtable_visit_argannotations(st, a->args)) return 0; - if (a->vararg && a->vararg->annotation) + if (a->vararg && a->vararg->annotation) { + st->st_cur->ste_annotations_used = 1; VISIT(st, expr, a->vararg->annotation); - if (a->kwarg && a->kwarg->annotation) + } + if (a->kwarg && a->kwarg->annotation) { + st->st_cur->ste_annotations_used = 1; VISIT(st, expr, a->kwarg->annotation); + } if (a->kwonlyargs && !symtable_visit_argannotations(st, a->kwonlyargs)) return 0; - if (future_annotations && !symtable_exit_block(st)) { - VISIT_QUIT(st, 0); + if (returns) { + st->st_cur->ste_annotations_used = 1; + VISIT(st, expr, returns); } - if (returns && !symtable_visit_annotation(st, returns)) { + if (!symtable_exit_block(st)) { VISIT_QUIT(st, 0); } return 1; From ab9359cd30bb39e92bfa03d4a6de799be16d75bd Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 10:46:59 -0400 Subject: [PATCH 10/61] Raise AssertionError on the wrong format --- Python/compile.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index c76c9b146c9c34..dcc97244ddc6cf 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -142,6 +142,15 @@ typedef _PyInstructionSequence instr_sequence; #define INITIAL_INSTR_SEQUENCE_SIZE 100 #define INITIAL_INSTR_SEQUENCE_LABELS_MAP_SIZE 10 +static const int compare_masks[] = { + [Py_LT] = COMPARISON_LESS_THAN, + [Py_LE] = COMPARISON_LESS_THAN | COMPARISON_EQUALS, + [Py_EQ] = COMPARISON_EQUALS, + [Py_NE] = COMPARISON_NOT_EQUALS, + [Py_GT] = COMPARISON_GREATER_THAN, + [Py_GE] = COMPARISON_GREATER_THAN | COMPARISON_EQUALS, +}; + /* * Resize the array if index is out of range. * @@ -1996,6 +2005,7 @@ compiler_visit_annotations(struct compiler *c, location loc, return ERROR; } assert(ste != NULL); + NEW_JUMP_TARGET_LABEL(c, raise_notimp); if (!future_annotations && ste->ste_annotations_used) { PyObject *annotations_name = PyUnicode_FromFormat("", ste->ste_name); @@ -2010,10 +2020,10 @@ compiler_visit_annotations(struct compiler *c, location loc, Py_DECREF(annotations_name); c->u->u_metadata.u_posonlyargcount = 1; _Py_DECLARE_STR(format, ".format"); - // RETURN_IF_ERROR(compiler_nameop(c, loc, &_Py_STR(format), Load)); - // ADDOP(c, loc, POP_TOP); - // ADDOP_LOAD_CONST(c, loc, _PyLong_GetOne()); - // TODO: emit "if .format != 1: raise NotImplementedError()" + ADDOP_I(c, loc, LOAD_FAST, 0); + ADDOP_LOAD_CONST(c, loc, _PyLong_GetOne()); + ADDOP_I(c, loc, COMPARE_OP, (Py_EQ << 5) | compare_masks[Py_EQ]); + ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, raise_notimp); } RETURN_IF_ERROR( @@ -2049,6 +2059,9 @@ compiler_visit_annotations(struct compiler *c, location loc, if (ste->ste_annotations_used) { ADDOP_I(c, loc, BUILD_MAP, annotations_len); ADDOP_IN_SCOPE(c, loc, RETURN_VALUE); + USE_LABEL(c, raise_notimp); + ADDOP_IN_SCOPE(c, loc, LOAD_ASSERTION_ERROR); + ADDOP_I(c, loc, RAISE_VARARGS, 1); PyCodeObject *co = optimize_and_assemble(c, 1); compiler_exit_scope(c); if (co == NULL) { @@ -2885,15 +2898,6 @@ check_compare(struct compiler *c, expr_ty e) return SUCCESS; } -static const int compare_masks[] = { - [Py_LT] = COMPARISON_LESS_THAN, - [Py_LE] = COMPARISON_LESS_THAN | COMPARISON_EQUALS, - [Py_EQ] = COMPARISON_EQUALS, - [Py_NE] = COMPARISON_NOT_EQUALS, - [Py_GT] = COMPARISON_GREATER_THAN, - [Py_GE] = COMPARISON_GREATER_THAN | COMPARISON_EQUALS, -}; - static int compiler_addcompare(struct compiler *c, location loc, cmpop_ty op) { From e50cd62156cc6e2eca313ca775091cf9ebcd6665 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 15:51:36 -0400 Subject: [PATCH 11/61] Modules and classes --- Python/compile.c | 216 ++++++++++++++++++++++++++++++---------------- Python/symtable.c | 1 - 2 files changed, 142 insertions(+), 75 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index dcc97244ddc6cf..cec332d12d47e4 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -380,6 +380,8 @@ static int compiler_pattern(struct compiler *, pattern_ty, pattern_context *); static int compiler_match(struct compiler *, stmt_ty); static int compiler_pattern_subpattern(struct compiler *, pattern_ty, pattern_context *); +static int compiler_make_closure(struct compiler *c, location loc, + PyCodeObject *co, Py_ssize_t flags); static PyCodeObject *optimize_and_assemble(struct compiler *, int addNone); @@ -1631,6 +1633,124 @@ compiler_unwind_fblock_stack(struct compiler *c, location *ploc, return SUCCESS; } +static int +compiler_setup_annotations_scope(struct compiler *c, location loc, + void *key, jump_target_label label) +{ + PyObject *annotations_name = PyUnicode_FromFormat( + "", c->u->u_ste->ste_name); + if (!annotations_name) { + return ERROR; + } + if (compiler_enter_scope(c, annotations_name, COMPILER_SCOPE_ANNOTATIONS, + key, loc.lineno) == -1) { + Py_DECREF(annotations_name); + return ERROR; + } + Py_DECREF(annotations_name); + c->u->u_metadata.u_posonlyargcount = 1; + _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_EQ << 5) | compare_masks[Py_EQ]); + ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, label); + return 0; +} + +static int +compiler_leave_annotations_scope(struct compiler *c, location loc, + int annotations_len, jump_target_label label) +{ + ADDOP_I(c, loc, BUILD_MAP, annotations_len); + ADDOP_IN_SCOPE(c, loc, RETURN_VALUE); + USE_LABEL(c, label); + ADDOP_IN_SCOPE(c, loc, LOAD_ASSERTION_ERROR); + ADDOP_I(c, loc, RAISE_VARARGS, 1); + PyCodeObject *co = optimize_and_assemble(c, 1); + compiler_exit_scope(c); + if (co == NULL) { + return ERROR; + } + if (compiler_make_closure(c, loc, co, 0) < 0) { + Py_DECREF(co); + return ERROR; + } + Py_DECREF(co); + return 0; +} + +static int +compiler_collect_annotations(struct compiler *c, asdl_stmt_seq *stmts, + int *annotations_len) +{ + for (int i = 0; i < asdl_seq_LEN(stmts); i++) { + stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, i); + switch (st->kind) { + case AnnAssign_kind: + if (st->v.AnnAssign.target->kind == Name_kind) { + PyObject *mangled = _Py_Mangle(c->u->u_private, st->v.AnnAssign.target->v.Name.id); + ADDOP_LOAD_CONST_NEW(c, LOC(st), mangled); + VISIT(c, expr, st->v.AnnAssign.annotation); + *annotations_len += 1; + } + break; + case For_kind: + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.For.body, annotations_len)); + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.For.orelse, annotations_len)); + break; + case AsyncFor_kind: + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.AsyncFor.body, annotations_len)); + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.AsyncFor.orelse, annotations_len)); + break; + case While_kind: + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.While.body, annotations_len)); + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.While.orelse, annotations_len)); + break; + case If_kind: + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.If.body, annotations_len)); + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.If.orelse, annotations_len)); + break; + case With_kind: + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.With.body, annotations_len)); + break; + case AsyncWith_kind: + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.AsyncWith.body, annotations_len)); + break; + case Match_kind: + for (int j = 0; j < asdl_seq_LEN(st->v.Match.cases); j++) { + match_case_ty match_case = (match_case_ty)asdl_seq_GET( + st->v.Match.cases, j); + RETURN_IF_ERROR(compiler_collect_annotations(c, match_case->body, annotations_len)); + } + break; + case Try_kind: + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.Try.body, annotations_len)); + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.Try.orelse, annotations_len)); + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.Try.finalbody, annotations_len)); + for (int j = 0; j < asdl_seq_LEN(st->v.Try.handlers); j++) { + excepthandler_ty handler = (excepthandler_ty)asdl_seq_GET( + st->v.Try.handlers, j); + RETURN_IF_ERROR(compiler_collect_annotations(c, handler->v.ExceptHandler.body, annotations_len)); + } + break; + case TryStar_kind: + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.TryStar.body, annotations_len)); + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.TryStar.orelse, annotations_len)); + RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.TryStar.finalbody, annotations_len)); + for (int j = 0; j < asdl_seq_LEN(st->v.TryStar.handlers); j++) { + excepthandler_ty handler = (excepthandler_ty)asdl_seq_GET( + st->v.Try.handlers, j); + RETURN_IF_ERROR(compiler_collect_annotations(c, handler->v.ExceptHandler.body, annotations_len)); + } + break; + default: + break; + } + } + return SUCCESS; + +} + /* Compile a sequence of statements, checking for a docstring and for annotations. */ @@ -1639,17 +1759,11 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) { /* Set current line number to the line number of first statement. - This way line number for SETUP_ANNOTATIONS will always - coincide with the line number of first "real" statement in module. If body is empty, then lineno will be set later in optimize_and_assemble. */ if (c->u->u_scope_type == COMPILER_SCOPE_MODULE && asdl_seq_LEN(stmts)) { stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, 0); loc = LOC(st); } - /* Every annotated class and module should have __annotations__. */ - if (find_ann(stmts)) { - ADDOP(c, loc, SETUP_ANNOTATIONS); - } if (!asdl_seq_LEN(stmts)) { return SUCCESS; } @@ -1674,6 +1788,21 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(stmts); i++) { VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i)); } + if (c->u->u_ste->ste_annotation_block != NULL) { + NEW_JUMP_TARGET_LABEL(c, raise_notimp); + void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); + RETURN_IF_ERROR(compiler_setup_annotations_scope(c, loc, key, raise_notimp)); + int annotations_len = 0; + RETURN_IF_ERROR( + compiler_collect_annotations(c, stmts, &annotations_len) + ); + RETURN_IF_ERROR( + compiler_leave_annotations_scope(c, loc, annotations_len, raise_notimp) + ); + RETURN_IF_ERROR( + compiler_nameop(c, loc, &_Py_ID(__annotate__), Store) + ); + } return SUCCESS; } @@ -2008,22 +2137,9 @@ compiler_visit_annotations(struct compiler *c, location loc, NEW_JUMP_TARGET_LABEL(c, raise_notimp); if (!future_annotations && ste->ste_annotations_used) { - PyObject *annotations_name = PyUnicode_FromFormat("", ste->ste_name); - if (!annotations_name) { - return ERROR; - } - if (compiler_enter_scope(c, annotations_name, COMPILER_SCOPE_ANNOTATIONS, - (void *)args, loc.lineno) == -1) { - Py_DECREF(annotations_name); - return ERROR; - } - Py_DECREF(annotations_name); - c->u->u_metadata.u_posonlyargcount = 1; - _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_EQ << 5) | compare_masks[Py_EQ]); - ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, raise_notimp); + RETURN_IF_ERROR( + compiler_setup_annotations_scope(c, loc, (void *)args, raise_notimp) + ); } RETURN_IF_ERROR( @@ -2057,21 +2173,9 @@ compiler_visit_annotations(struct compiler *c, location loc, else { assert(ste != NULL); if (ste->ste_annotations_used) { - ADDOP_I(c, loc, BUILD_MAP, annotations_len); - ADDOP_IN_SCOPE(c, loc, RETURN_VALUE); - USE_LABEL(c, raise_notimp); - ADDOP_IN_SCOPE(c, loc, LOAD_ASSERTION_ERROR); - ADDOP_I(c, loc, RAISE_VARARGS, 1); - PyCodeObject *co = optimize_and_assemble(c, 1); - compiler_exit_scope(c); - if (co == NULL) { - return ERROR; - } - if (compiler_make_closure(c, loc, co, 0) < 0) { - Py_DECREF(co); - return ERROR; - } - Py_DECREF(co); + RETURN_IF_ERROR( + compiler_leave_annotations_scope(c, loc, annotations_len, raise_notimp) + ); return MAKE_FUNCTION_ANNOTATE; } } @@ -6485,23 +6589,6 @@ check_ann_expr(struct compiler *c, expr_ty e) return SUCCESS; } -static int -check_annotation(struct compiler *c, stmt_ty s) -{ - /* Annotations of complex targets does not produce anything - under annotations future */ - if (c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) { - return SUCCESS; - } - - /* Annotations are only evaluated in a module or class. */ - if (c->u->u_scope_type == COMPILER_SCOPE_MODULE || - c->u->u_scope_type == COMPILER_SCOPE_CLASS) { - return check_ann_expr(c, s->v.AnnAssign.annotation); - } - return SUCCESS; -} - static int check_ann_subscr(struct compiler *c, expr_ty e) { @@ -6537,7 +6624,6 @@ compiler_annassign(struct compiler *c, stmt_ty s) { location loc = LOC(s); expr_ty targ = s->v.AnnAssign.target; - PyObject* mangled; assert(s->kind == AnnAssign_kind); @@ -6551,21 +6637,6 @@ compiler_annassign(struct compiler *c, stmt_ty s) if (forbidden_name(c, loc, targ->v.Name.id, Store)) { return ERROR; } - /* If we have a simple name in a module or class, store annotation. */ - // if (s->v.AnnAssign.simple && - // (c->u->u_scope_type == COMPILER_SCOPE_MODULE || - // c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { - // if (c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) { - // VISIT(c, annexpr, s->v.AnnAssign.annotation) - // } - // else { - // VISIT(c, expr, s->v.AnnAssign.annotation); - // } - // ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); - // mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id); - // ADDOP_LOAD_CONST_NEW(c, loc, mangled); - // ADDOP(c, loc, STORE_SUBSCR); - // } break; case Attribute_kind: if (forbidden_name(c, loc, targ->v.Attribute.attr, Store)) { @@ -6589,10 +6660,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) targ->kind); return ERROR; } - /* Annotation is evaluated last. */ - // if (!s->v.AnnAssign.simple && check_annotation(c, s) < 0) { - // return ERROR; - // } + /* For non-simple AnnAssign, the annotation is not evaluated. */ return SUCCESS; } diff --git a/Python/symtable.c b/Python/symtable.c index 438751ac1aec8d..0f594c27186d5b 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2476,7 +2476,6 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, VISIT_QUIT(st, 0); } else { - printf("enter block %p\n", key); if (st->st_cur->ste_annotation_block == NULL) { if (!symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, key, LOCATION(annotation))) { From afae5c07ede77b73d2d423e86e8cc57923df186b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 16:10:56 -0400 Subject: [PATCH 12/61] attempts --- Lib/_pyrepl/readline.py | 4 ++-- Lib/inspect.py | 8 +------- Python/compile.c | 13 +++++++++++++ Python/future.c | 4 +--- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Lib/_pyrepl/readline.py b/Lib/_pyrepl/readline.py index 0adecf235a4eb4..5771cbb404b8b4 100644 --- a/Lib/_pyrepl/readline.py +++ b/Lib/_pyrepl/readline.py @@ -84,8 +84,8 @@ @dataclass class ReadlineConfig: - readline_completer: Completer | None = readline.get_completer() - completer_delims: frozenset[str] = frozenset(" \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?") + #readline_completer: Completer | None = readline.get_completer() + pass #completer_delims: frozenset[str] = frozenset(" \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?") @dataclass(kw_only=True) diff --git a/Lib/inspect.py b/Lib/inspect.py index e6e49a4ffa673a..0ca3c3ff44e3ba 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -220,13 +220,7 @@ def get_annotations(obj, *, globals=None, locals=None, eval_str=False): """ if isinstance(obj, type): # class - obj_dict = getattr(obj, '__dict__', None) - if obj_dict and hasattr(obj_dict, 'get'): - ann = obj_dict.get('__annotations__', None) - if isinstance(ann, types.GetSetDescriptorType): - ann = None - else: - ann = None + ann = obj.__annotations__ obj_globals = None module_name = getattr(obj, '__module__', None) diff --git a/Python/compile.c b/Python/compile.c index cec332d12d47e4..be6f2b6b5280dd 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -4403,6 +4403,7 @@ compiler_nameop(struct compiler *c, location loc, } /* XXX Leave assert here, but handle __doc__ and the like better */ + printf("name %s %s\n", PyUnicode_AsUTF8(name), PyUnicode_AsUTF8(c->u->u_ste->ste_name)); assert(scope || PyUnicode_READ_CHAR(name, 0) == '_'); switch (optype) { @@ -6624,6 +6625,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) { location loc = LOC(s); expr_ty targ = s->v.AnnAssign.target; + PyObject *mangled; assert(s->kind == AnnAssign_kind); @@ -6637,6 +6639,17 @@ compiler_annassign(struct compiler *c, stmt_ty s) if (forbidden_name(c, loc, targ->v.Name.id, Store)) { return ERROR; } + /* If we have a simple name in a module or class, store annotation. */ + if (c->c_future.ff_features & CO_FUTURE_ANNOTATIONS && + s->v.AnnAssign.simple && + (c->u->u_scope_type == COMPILER_SCOPE_MODULE || + c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { + VISIT(c, annexpr, s->v.AnnAssign.annotation); + ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); + mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id); + ADDOP_LOAD_CONST_NEW(c, loc, mangled); + ADDOP(c, loc, STORE_SUBSCR); + } break; case Attribute_kind: if (forbidden_name(c, loc, targ->v.Attribute.attr, Store)) { diff --git a/Python/future.c b/Python/future.c index 9e528bf121346a..8d94d515605dcd 100644 --- a/Python/future.c +++ b/Python/future.c @@ -37,9 +37,7 @@ future_check_features(_PyFutureFeatures *ff, stmt_ty s, PyObject *filename) } else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) { continue; } else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) { - // For now, we ignore this future. We may make it stringify again - // in the future. - // ff->ff_features |= CO_FUTURE_ANNOTATIONS; + ff->ff_features |= CO_FUTURE_ANNOTATIONS; } else if (strcmp(feature, "braces") == 0) { PyErr_SetString(PyExc_SyntaxError, "not a chance"); From e5a7b1a86faad709e2f16bbc55a2d535cc29ccdf Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 16:43:26 -0400 Subject: [PATCH 13/61] Fixes --- Lib/_pyrepl/readline.py | 4 ++-- Python/compile.c | 20 ++++++++++++++------ Python/symtable.c | 9 +++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Lib/_pyrepl/readline.py b/Lib/_pyrepl/readline.py index 5771cbb404b8b4..0adecf235a4eb4 100644 --- a/Lib/_pyrepl/readline.py +++ b/Lib/_pyrepl/readline.py @@ -84,8 +84,8 @@ @dataclass class ReadlineConfig: - #readline_completer: Completer | None = readline.get_completer() - pass #completer_delims: frozenset[str] = frozenset(" \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?") + readline_completer: Completer | None = readline.get_completer() + completer_delims: frozenset[str] = frozenset(" \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?") @dataclass(kw_only=True) diff --git a/Python/compile.c b/Python/compile.c index be6f2b6b5280dd..5be0beadacc162 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1759,11 +1759,17 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) { /* Set current line number to the line number of first statement. + This way line number for SETUP_ANNOTATIONS will always + coincide with the line number of first "real" statement in module. If body is empty, then lineno will be set later in optimize_and_assemble. */ if (c->u->u_scope_type == COMPILER_SCOPE_MODULE && asdl_seq_LEN(stmts)) { stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, 0); loc = LOC(st); } + /* Every annotated class and module should have __annotations__. */ + if ((c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && find_ann(stmts)) { + ADDOP(c, loc, SETUP_ANNOTATIONS); + } if (!asdl_seq_LEN(stmts)) { return SUCCESS; } @@ -1788,7 +1794,8 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(stmts); i++) { VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i)); } - if (c->u->u_ste->ste_annotation_block != NULL) { + if (!(c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && + c->u->u_ste->ste_annotation_block != NULL) { NEW_JUMP_TARGET_LABEL(c, raise_notimp); void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); RETURN_IF_ERROR(compiler_setup_annotations_scope(c, loc, key, raise_notimp)); @@ -2167,8 +2174,10 @@ compiler_visit_annotations(struct compiler *c, location loc, compiler_visit_argannotation(c, &_Py_ID(return), returns, &annotations_len, loc)); if (future_annotations) { - ADDOP_I(c, loc, BUILD_TUPLE, annotations_len * 2); - return MAKE_FUNCTION_ANNOTATIONS; + if (annotations_len) { + ADDOP_I(c, loc, BUILD_TUPLE, annotations_len * 2); + return MAKE_FUNCTION_ANNOTATIONS; + } } else { assert(ste != NULL); @@ -4403,7 +4412,6 @@ compiler_nameop(struct compiler *c, location loc, } /* XXX Leave assert here, but handle __doc__ and the like better */ - printf("name %s %s\n", PyUnicode_AsUTF8(name), PyUnicode_AsUTF8(c->u->u_ste->ste_name)); assert(scope || PyUnicode_READ_CHAR(name, 0) == '_'); switch (optype) { @@ -6625,7 +6633,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) { location loc = LOC(s); expr_ty targ = s->v.AnnAssign.target; - PyObject *mangled; + PyObject* mangled; assert(s->kind == AnnAssign_kind); @@ -6648,7 +6656,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id); ADDOP_LOAD_CONST_NEW(c, loc, mangled); - ADDOP(c, loc, STORE_SUBSCR); + ADDOP(c, loc, STORE_SUBSCR); } break; case Attribute_kind: diff --git a/Python/symtable.c b/Python/symtable.c index 0f594c27186d5b..6895393b0d5c27 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2470,10 +2470,11 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, struct _symtable_entry *parent_ste, void *key) { int future_annotations = st->st_future->ff_features & CO_FUTURE_ANNOTATIONS; - if (future_annotations && - !symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, - key, LOCATION(annotation))) { - VISIT_QUIT(st, 0); + if (future_annotations) { + if(!symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, + key, LOCATION(annotation))) { + VISIT_QUIT(st, 0); + } } else { if (st->st_cur->ste_annotation_block == NULL) { From 31a4471a3210628c67179a62cc727045ff956ffc Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 17:00:28 -0400 Subject: [PATCH 14/61] Fix code object name --- Python/compile.c | 11 +++++++---- Python/symtable.c | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 5be0beadacc162..8a83799071bd0a 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1635,10 +1635,11 @@ compiler_unwind_fblock_stack(struct compiler *c, location *ploc, static int compiler_setup_annotations_scope(struct compiler *c, location loc, - void *key, jump_target_label label) + void *key, jump_target_label label, + PyObject *name) { PyObject *annotations_name = PyUnicode_FromFormat( - "", c->u->u_ste->ste_name); + "", name); if (!annotations_name) { return ERROR; } @@ -1798,7 +1799,8 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) c->u->u_ste->ste_annotation_block != NULL) { NEW_JUMP_TARGET_LABEL(c, raise_notimp); void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); - RETURN_IF_ERROR(compiler_setup_annotations_scope(c, loc, key, raise_notimp)); + RETURN_IF_ERROR(compiler_setup_annotations_scope(c, loc, key, raise_notimp, + c->u->u_ste->ste_name)); int annotations_len = 0; RETURN_IF_ERROR( compiler_collect_annotations(c, stmts, &annotations_len) @@ -2145,7 +2147,8 @@ compiler_visit_annotations(struct compiler *c, location loc, if (!future_annotations && ste->ste_annotations_used) { RETURN_IF_ERROR( - compiler_setup_annotations_scope(c, loc, (void *)args, raise_notimp) + compiler_setup_annotations_scope(c, loc, (void *)args, raise_notimp, + ste->ste_name) ); } diff --git a/Python/symtable.c b/Python/symtable.c index 6895393b0d5c27..0d7baf71072b14 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2478,7 +2478,7 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, } else { if (st->st_cur->ste_annotation_block == NULL) { - if (!symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, + if (!symtable_enter_block(st, parent_ste->ste_name, AnnotationBlock, key, LOCATION(annotation))) { VISIT_QUIT(st, 0); } From fbb1d888d9af1818a08b2ffd26c3e05baa4f8515 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 20:20:56 -0400 Subject: [PATCH 15/61] Start fixing test_type_annotations --- Lib/test/test_type_annotations.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index ef1569487de18c..2ad4ceb4dfcecc 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -3,6 +3,7 @@ import unittest from test.support import run_code + class TypeAnnotationTests(unittest.TestCase): def test_lazy_create_annotations(self): @@ -49,6 +50,7 @@ def test_annotations_are_created_correctly(self): class C: a:int=3 b:str=4 + self.assertEqual(C.__annotations__, {"a": int, "b": str}) self.assertTrue("__annotations__" in C.__dict__) del C.__annotations__ self.assertFalse("__annotations__" in C.__dict__) @@ -117,7 +119,9 @@ def check(self, code: str): if scope == "class": annotations = ns["C"].__annotations__ else: - annotations = ns["__annotations__"] + mod = types.ModuleType("mod") + mod.__dict__.update(ns) + annotations = mod.__annotations__ self.assertEqual(annotations, {"x": int}) def test_top_level(self): @@ -256,3 +260,22 @@ def check_annotations(self, f): # Setting f.__annotations__ also clears __annotate__ f.__annotations__ = {"z": 43} self.assertIs(f.__annotate__, None) + + +class DeferredEvaluationTests(unittest.TestCase): + def test_function(self): + def func(x: undefined, /, y: undefined, *args: undefined, z: undefined, **kwargs: undefined) -> undefined: + pass + + with self.assertRaises(NameError): + func.__annotations__ + + undefined = 1 + self.assertEqual(func.__annotations__, { + "x": 1, + "y": 1, + "args": 1, + "z": 1, + "kwargs": 1, + "return": 1, + }) From f452eb2e9233f0e7884b648f45a53cb6428fbaf4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 20:38:12 -0400 Subject: [PATCH 16/61] Fix class scoping --- Lib/test/test_type_annotations.py | 48 +++++++++++++++++++++++++++++++ Python/symtable.c | 18 +++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 2ad4ceb4dfcecc..3b9552f98ea4c7 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -279,3 +279,51 @@ def func(x: undefined, /, y: undefined, *args: undefined, z: undefined, **kwargs "kwargs": 1, "return": 1, }) + + def test_async_function(self): + async def func(x: undefined, /, y: undefined, *args: undefined, z: undefined, **kwargs: undefined) -> undefined: + pass + + with self.assertRaises(NameError): + func.__annotations__ + + undefined = 1 + self.assertEqual(func.__annotations__, { + "x": 1, + "y": 1, + "args": 1, + "z": 1, + "kwargs": 1, + "return": 1, + }) + + def test_class(self): + class X: + a: undefined + + with self.assertRaises(NameError): + X.__annotations__ + + undefined = 1 + self.assertEqual(X.__annotations__, {"a": 1}) + + def test_module(self): + ns = run_code("x: undefined = 1") + anno = ns["__annotate__"] + with self.assertRaises(AssertionError): # TODO NotImplementedError + anno(2) + + with self.assertRaises(NameError): + anno(1) + + ns["undefined"] = 1 + self.assertEqual(anno(1), {"x": 1}) + + def test_class_scoping(self): + class Outer: + def meth(self, x: Nested): ... + x: Nested + class Nested: ... + + self.assertEqual(Outer.meth.__annotations__, {"x": Outer.Nested}) + self.assertEqual(Outer.__annotations__, {"x": Outer.Nested}) diff --git a/Python/symtable.c b/Python/symtable.c index 0d7baf71072b14..4aa18518ae688b 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -542,6 +542,7 @@ int _PyST_IsFunctionLike(PySTEntryObject *ste) { return ste->ste_type == FunctionBlock + || ste->ste_type == AnnotationBlock || ste->ste_type == TypeVarBoundBlock || ste->ste_type == TypeAliasBlock || ste->ste_type == TypeParamBlock; @@ -2478,12 +2479,20 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, } else { if (st->st_cur->ste_annotation_block == NULL) { + _Py_block_ty current_type = st->st_cur->ste_type; if (!symtable_enter_block(st, parent_ste->ste_name, AnnotationBlock, key, LOCATION(annotation))) { VISIT_QUIT(st, 0); } parent_ste->ste_annotation_block = (struct _symtable_entry *)Py_NewRef(st->st_cur); + if (current_type == ClassBlock) { + st->st_cur->ste_can_see_class_scope = 1; + if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(annotation))) { + return 0; + } + } + _Py_DECLARE_STR(format, ".format"); // We need to insert code that reads this "parameter" to the function. if (!symtable_add_def(st, &_Py_STR(format), DEF_PARAM, @@ -2531,10 +2540,17 @@ static int symtable_visit_annotations(struct symtable *st, stmt_ty o, arguments_ty a, expr_ty returns, struct _symtable_entry *function_ste) { + _Py_block_ty current_type = st->st_cur->ste_type; if (!symtable_enter_block(st, function_ste->ste_name, AnnotationBlock, (void *)a, LOCATION(o))) { VISIT_QUIT(st, 0); } + if (current_type == ClassBlock) { + st->st_cur->ste_can_see_class_scope = 1; + if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(o))) { + return 0; + } + } _Py_DECLARE_STR(format, ".format"); // We need to insert code that reads this "parameter" to the function. if (!symtable_add_def(st, &_Py_STR(format), DEF_PARAM, LOCATION(o))) { @@ -2793,7 +2809,7 @@ symtable_visit_dictcomp(struct symtable *st, expr_ty e) static int symtable_raise_if_annotation_block(struct symtable *st, const char *name, expr_ty e) { - enum _block_type type = st->st_cur->ste_type; + _Py_block_ty type = st->st_cur->ste_type; if (type == AnnotationBlock) PyErr_Format(PyExc_SyntaxError, ANNOTATION_NOT_ALLOWED, name); else if (type == TypeVarBoundBlock) From 8c4b4e3c0fb49d1f5f3e8853d39cd2be18e98050 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 20:40:08 -0400 Subject: [PATCH 17/61] unyielding --- Lib/test/test_type_annotations.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 3b9552f98ea4c7..4374afd6f57efb 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -1,7 +1,7 @@ import textwrap import types import unittest -from test.support import run_code +from test.support import run_code, check_syntax_error class TypeAnnotationTests(unittest.TestCase): @@ -327,3 +327,9 @@ class Nested: ... self.assertEqual(Outer.meth.__annotations__, {"x": Outer.Nested}) self.assertEqual(Outer.__annotations__, {"x": Outer.Nested}) + + def test_no_exotic_expressions(self): + check_syntax_error(self, "def func(x: (yield)): ...", "yield expression cannot be used within an annotation") + check_syntax_error(self, "def func(x: (yield from x)): ...", "yield expression cannot be used within an annotation") + check_syntax_error(self, "def func(x: (y := 3)): ...", "named expression cannot be used within an annotation") + check_syntax_error(self, "def func(x: (await 42)): ...", "await expression cannot be used within an annotation") From cbf9a3da172c7648006ca50868a5b425249b12bb Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 20:56:10 -0400 Subject: [PATCH 18/61] Fix test_typing --- Lib/test/test_typing.py | 4 ++-- Lib/test/typinganndata/ann_module.py | 4 ---- Lib/typing.py | 19 +++++++++++++++---- Python/compile.c | 5 +++-- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index dac55ceb9e99e0..9800b3b6a7da29 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6634,7 +6634,7 @@ def test_get_type_hints_from_various_objects(self): gth(None) def test_get_type_hints_modules(self): - ann_module_type_hints = {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str, 'u': int | float} + ann_module_type_hints = {'f': Tuple[int, int], 'x': int, 'y': str, 'u': int | float} self.assertEqual(gth(ann_module), ann_module_type_hints) self.assertEqual(gth(ann_module2), {}) self.assertEqual(gth(ann_module3), {}) @@ -6652,7 +6652,7 @@ def test_get_type_hints_classes(self): self.assertEqual(gth(ann_module.C), # gth will find the right globalns {'y': Optional[ann_module.C]}) self.assertIsInstance(gth(ann_module.j_class), dict) - self.assertEqual(gth(ann_module.M), {'123': 123, 'o': type}) + self.assertEqual(gth(ann_module.M), {'o': type}) self.assertEqual(gth(ann_module.D), {'j': str, 'k': str, 'y': Optional[ann_module.C]}) self.assertEqual(gth(ann_module.Y), {'z': int}) diff --git a/Lib/test/typinganndata/ann_module.py b/Lib/test/typinganndata/ann_module.py index 5081e6b58345a9..e1a1792cb4a867 100644 --- a/Lib/test/typinganndata/ann_module.py +++ b/Lib/test/typinganndata/ann_module.py @@ -8,8 +8,6 @@ from typing import Optional from functools import wraps -__annotations__[1] = 2 - class C: x = 5; y: Optional['C'] = None @@ -18,8 +16,6 @@ class C: x: int = 5; y: str = x; f: Tuple[int, int] class M(type): - - __annotations__['123'] = 123 o: type = object (pars): bool = True diff --git a/Lib/typing.py b/Lib/typing.py index be49aa63464f05..dcbacde3099cfc 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2412,7 +2412,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): base_globals = getattr(sys.modules.get(base.__module__, None), '__dict__', {}) else: base_globals = globalns - ann = base.__dict__.get('__annotations__', {}) + ann = getattr(base, '__annotations__', {}) if isinstance(ann, types.GetSetDescriptorType): ann = {} base_locals = dict(vars(base)) if localns is None else localns @@ -2970,7 +2970,12 @@ def __new__(cls, typename, bases, ns): raise TypeError( 'can only inherit from a NamedTuple type and Generic') bases = tuple(tuple if base is _NamedTuple else base for base in bases) - types = ns.get('__annotations__', {}) + if "__annotations__" in ns: + types = ns["__annotations__"] + elif "__annotate__" in ns: + types = ns["__annotate__"](1) + else: + types = {} default_names = [] for field_name in types: if field_name in ns: @@ -3131,7 +3136,12 @@ def __new__(cls, name, bases, ns, total=True): tp_dict.__orig_bases__ = bases annotations = {} - own_annotations = ns.get('__annotations__', {}) + if "__annotations__" in ns: + own_annotations = ns["__annotations__"] + elif "__annotate__" in ns: + own_annotations = ns["__annotate__"](1) + else: + own_annotations = {} msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" own_annotations = { n: _type_check(tp, msg, module=tp_dict.__module__) @@ -3143,7 +3153,8 @@ def __new__(cls, name, bases, ns, total=True): mutable_keys = set() for base in bases: - annotations.update(base.__dict__.get('__annotations__', {})) + # TODO: preserve laziness + annotations.update(base.__annotations__) base_required = base.__dict__.get('__required_keys__', set()) required_keys |= base_required diff --git a/Python/compile.c b/Python/compile.c index 8a83799071bd0a..4afa9ed74024bc 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1688,7 +1688,8 @@ compiler_collect_annotations(struct compiler *c, asdl_stmt_seq *stmts, stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, i); switch (st->kind) { case AnnAssign_kind: - if (st->v.AnnAssign.target->kind == Name_kind) { + // Only "simple" names (i.e., unparenthesized names) are stored. + if (st->v.AnnAssign.simple) { PyObject *mangled = _Py_Mangle(c->u->u_private, st->v.AnnAssign.target->v.Name.id); ADDOP_LOAD_CONST_NEW(c, LOC(st), mangled); VISIT(c, expr, st->v.AnnAssign.annotation); @@ -6651,7 +6652,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) return ERROR; } /* If we have a simple name in a module or class, store annotation. */ - if (c->c_future.ff_features & CO_FUTURE_ANNOTATIONS && + if ((c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && s->v.AnnAssign.simple && (c->u->u_scope_type == COMPILER_SCOPE_MODULE || c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { From 5d182fceeadec2fe96425f1500a19aa2ff5007dc Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 20:57:53 -0400 Subject: [PATCH 19/61] Fix test_type_parmas --- Python/symtable.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/symtable.c b/Python/symtable.c index 4aa18518ae688b..579d047d526218 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2540,12 +2540,13 @@ static int symtable_visit_annotations(struct symtable *st, stmt_ty o, arguments_ty a, expr_ty returns, struct _symtable_entry *function_ste) { + int is_in_class = st->st_cur->ste_can_see_class_scope; _Py_block_ty current_type = st->st_cur->ste_type; if (!symtable_enter_block(st, function_ste->ste_name, AnnotationBlock, (void *)a, LOCATION(o))) { VISIT_QUIT(st, 0); } - if (current_type == ClassBlock) { + if (is_in_class || current_type == ClassBlock) { st->st_cur->ste_can_see_class_scope = 1; if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(o))) { return 0; From ce98c1907a0c732391114fc66f36e996bbb80fea Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 21:16:45 -0400 Subject: [PATCH 20/61] Add test, add to inspect --- Lib/inspect.py | 11 ++++++++++ Lib/test/test_inspect/test_inspect.py | 5 +++++ Lib/test/test_type_annotations.py | 31 ++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index 0ca3c3ff44e3ba..725c5efb4787d1 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -38,6 +38,7 @@ "AGEN_CREATED", "AGEN_RUNNING", "AGEN_SUSPENDED", + "AnnotationsFormat", "ArgInfo", "Arguments", "Attribute", @@ -61,6 +62,7 @@ "ClassFoundException", "ClosureVars", "EndOfBlock", + "FORWARDREF", "FrameInfo", "FullArgSpec", "GEN_CLOSED", @@ -134,9 +136,11 @@ "istraceback", "markcoroutinefunction", "signature", + "SOURCE", "stack", "trace", "unwrap", + "VALUE", "walktree", ] @@ -173,6 +177,13 @@ TPFLAGS_IS_ABSTRACT = 1 << 20 +@enum.global_enum +class AnnotationsFormat(enum.IntEnum): + VALUE = 1 + FORWARDREF = 2 + SOURCE = 3 + + def get_annotations(obj, *, globals=None, locals=None, eval_str=False): """Compute the annotations dict for an object. diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 011d42f34b6461..546c9fea390abd 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1592,6 +1592,11 @@ class C(metaclass=M): attrs = [a[0] for a in inspect.getmembers(C)] self.assertNotIn('missing', attrs) + def test_annotation_format(self): + self.assertIs(inspect.VALUE, inspect.AnnotationsFormat.VALUE) + self.assertEqual(inspect.VALUE.value, 1) + self.assertEqual(inspect.VALUE, 1) + def test_get_annotations_with_stock_annotations(self): def foo(a:int, b:str): pass self.assertEqual(inspect.get_annotations(foo), {'a': int, 'b': str}) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 4374afd6f57efb..f84f34e394931a 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -108,6 +108,13 @@ class D(metaclass=C): self.assertEqual(D.__annotations__, {}) +def build_module(code: str, name: str = "top") -> types.ModuleType: + ns = run_code(code) + mod = types.ModuleType(name) + mod.__dict__.update(ns) + return mod + + class TestSetupAnnotations(unittest.TestCase): def check(self, code: str): code = textwrap.dedent(code) @@ -115,13 +122,10 @@ def check(self, code: str): with self.subTest(scope=scope): if scope == "class": code = f"class C:\n{textwrap.indent(code, ' ')}" - ns = run_code(code) - if scope == "class": + ns = run_code(code) annotations = ns["C"].__annotations__ else: - mod = types.ModuleType("mod") - mod.__dict__.update(ns) - annotations = mod.__annotations__ + annotations = build_module(code).__annotations__ self.assertEqual(annotations, {"x": int}) def test_top_level(self): @@ -333,3 +337,20 @@ def test_no_exotic_expressions(self): check_syntax_error(self, "def func(x: (yield from x)): ...", "yield expression cannot be used within an annotation") check_syntax_error(self, "def func(x: (y := 3)): ...", "named expression cannot be used within an annotation") check_syntax_error(self, "def func(x: (await 42)): ...", "await expression cannot be used within an annotation") + + def test_generated_annotate(self): + def func(x: int): + pass + class X: + x: int + mod = build_module("x: int") + for obj in (func, X, mod): + with self.subTest(obj=obj): + annotate = obj.__annotate__ + self.assertIsInstance(annotate, types.FunctionType) + self.assertEqual(annotate.__name__, f"") + with self.assertRaises(AssertionError): # TODO NotImplementedError + annotate(2) + with self.assertRaises(AssertionError): # TODO NotImplementedError + annotate(None) + self.assertEqual(annotate(1), {"x": int}) From e0578fcf1d8ecd3935dc3331fccf92fb68a2cb5f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 21:18:33 -0400 Subject: [PATCH 21/61] Use inspect constants --- Lib/test/test_type_annotations.py | 5 +++-- Lib/typing.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index f84f34e394931a..43b81f1470453e 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -1,3 +1,4 @@ +import inspect import textwrap import types import unittest @@ -350,7 +351,7 @@ class X: self.assertIsInstance(annotate, types.FunctionType) self.assertEqual(annotate.__name__, f"") with self.assertRaises(AssertionError): # TODO NotImplementedError - annotate(2) + annotate(inspect.FORWARDREF) with self.assertRaises(AssertionError): # TODO NotImplementedError annotate(None) - self.assertEqual(annotate(1), {"x": int}) + self.assertEqual(annotate(inspect.VALUE), {"x": int}) diff --git a/Lib/typing.py b/Lib/typing.py index dcbacde3099cfc..15307f28de6c8a 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -24,6 +24,7 @@ import collections.abc import copyreg import functools +import inspect import operator import sys import types @@ -2973,7 +2974,7 @@ def __new__(cls, typename, bases, ns): if "__annotations__" in ns: types = ns["__annotations__"] elif "__annotate__" in ns: - types = ns["__annotate__"](1) + types = ns["__annotate__"](inspect.VALUE) else: types = {} default_names = [] @@ -3139,7 +3140,7 @@ def __new__(cls, name, bases, ns, total=True): if "__annotations__" in ns: own_annotations = ns["__annotations__"] elif "__annotate__" in ns: - own_annotations = ns["__annotate__"](1) + own_annotations = ns["__annotate__"](inspect.VALUE) else: own_annotations = {} msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" From ed161677081e882529a327ea55d8c51b515370ed Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 21:24:51 -0400 Subject: [PATCH 22/61] test_grammar tweaks --- Lib/test/test_grammar.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index c72f4387108ca8..388bc72fe2385e 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -459,9 +459,9 @@ class CC(metaclass=CMeta): def test_var_annot_module_semantics(self): self.assertEqual(test.__annotations__, {}) self.assertEqual(ann_module.__annotations__, - {1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float}) + {'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float}) self.assertEqual(ann_module.M.__annotations__, - {'123': 123, 'o': type}) + {'o': type}) self.assertEqual(ann_module2.__annotations__, {}) def test_var_annot_in_module(self): @@ -476,13 +476,12 @@ def test_var_annot_in_module(self): ann_module3.D_bad_ann(5) def test_var_annot_simple_exec(self): - gns = {}; lns= {} + gns = {}; lns = {} exec("'docstring'\n" - "__annotations__[1] = 2\n" "x: int = 5\n", gns, lns) - self.assertEqual(lns["__annotations__"], {1: 2, 'x': int}) + self.assertEqual(lns["__annotate__"](inspect.VALUE), {'x': int}) with self.assertRaises(KeyError): - gns['__annotations__'] + gns['__annotate__'] def test_var_annot_custom_maps(self): # tests with custom locals() and __annotations__ From f38de202041cd2e20e218a263d9efb6679edd863 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 21:27:47 -0400 Subject: [PATCH 23/61] fix test_positional_only_arg --- Lib/test/test_positional_only_arg.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_positional_only_arg.py b/Lib/test/test_positional_only_arg.py index 1a193814d7535d..ad452c032d1614 100644 --- a/Lib/test/test_positional_only_arg.py +++ b/Lib/test/test_positional_only_arg.py @@ -2,6 +2,7 @@ import dis import pickle +import types import unittest from test.support import check_syntax_error @@ -440,7 +441,9 @@ def f(x: not (int is int), /): ... # without constant folding we end up with # COMPARE_OP(is), IS_OP (0) # with constant folding we should expect a IS_OP (1) - codes = [(i.opname, i.argval) for i in dis.get_instructions(g)] + code_obj = next(const for const in g.__code__.co_consts + if isinstance(const, types.CodeType) and "annotations" in const.co_name) + codes = [(i.opname, i.argval) for i in dis.get_instructions(code_obj)] self.assertNotIn(('UNARY_NOT', None), codes) self.assertIn(('IS_OP', 1), codes) From 87baca29bda95caf736c5ba687e4c18c64e9212d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 21:29:14 -0400 Subject: [PATCH 24/61] Fix test_module --- Lib/test/test_module/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_module/__init__.py b/Lib/test/test_module/__init__.py index 952ba43f72504d..56edd0c637f376 100644 --- a/Lib/test/test_module/__init__.py +++ b/Lib/test/test_module/__init__.py @@ -360,6 +360,8 @@ def test_annotations_are_created_correctly(self): ann_module4 = import_helper.import_fresh_module( 'test.typinganndata.ann_module4', ) + self.assertFalse("__annotations__" in ann_module4.__dict__) + self.assertEqual(ann_module4.__annotations__, {"a": int, "b": str}) self.assertTrue("__annotations__" in ann_module4.__dict__) del ann_module4.__annotations__ self.assertFalse("__annotations__" in ann_module4.__dict__) From 355d3dfee055c9972945aa2b9ef26258c8d41bc6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 21:52:18 -0400 Subject: [PATCH 25/61] Fix symtable tests --- Lib/symtable.py | 2 ++ Lib/test/test_symtable.py | 6 ++++-- Python/compile.c | 11 ++--------- Python/symtable.c | 18 ++++++++++++++++-- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Lib/symtable.py b/Lib/symtable.py index 17f820abd56660..cfd9d0f63bbf5d 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -218,6 +218,8 @@ def get_methods(self): if self.__methods is None: d = {} for st in self._table.children: + if st.type == _symtable.TYPE_ANNOTATION: + continue d[st.name] = 1 self.__methods = tuple(d) return self.__methods diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index 92b78a8086a83d..a2e83f40fac812 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -205,12 +205,14 @@ def test_assigned(self): def test_annotated(self): st1 = symtable.symtable('def f():\n x: int\n', 'test', 'exec') - st2 = st1.get_children()[0] + st2 = st1.get_children()[1] + self.assertEqual(st2.get_type(), "function") self.assertTrue(st2.lookup('x').is_local()) self.assertTrue(st2.lookup('x').is_annotated()) self.assertFalse(st2.lookup('x').is_global()) st3 = symtable.symtable('def f():\n x = 1\n', 'test', 'exec') - st4 = st3.get_children()[0] + st4 = st3.get_children()[1] + self.assertEqual(st4.get_type(), "function") self.assertTrue(st4.lookup('x').is_local()) self.assertFalse(st4.lookup('x').is_annotated()) diff --git a/Python/compile.c b/Python/compile.c index 4afa9ed74024bc..2a4dea76973c1c 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1638,17 +1638,10 @@ compiler_setup_annotations_scope(struct compiler *c, location loc, void *key, jump_target_label label, PyObject *name) { - PyObject *annotations_name = PyUnicode_FromFormat( - "", name); - if (!annotations_name) { - return ERROR; - } - if (compiler_enter_scope(c, annotations_name, COMPILER_SCOPE_ANNOTATIONS, + if (compiler_enter_scope(c, name, COMPILER_SCOPE_ANNOTATIONS, key, loc.lineno) == -1) { - Py_DECREF(annotations_name); return ERROR; } - Py_DECREF(annotations_name); c->u->u_metadata.u_posonlyargcount = 1; _Py_DECLARE_STR(format, ".format"); ADDOP_I(c, loc, LOAD_FAST, 0); @@ -1801,7 +1794,7 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) NEW_JUMP_TARGET_LABEL(c, raise_notimp); void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); RETURN_IF_ERROR(compiler_setup_annotations_scope(c, loc, key, raise_notimp, - c->u->u_ste->ste_name)); + c->u->u_ste->ste_annotation_block->ste_name)); int annotations_len = 0; RETURN_IF_ERROR( compiler_collect_annotations(c, stmts, &annotations_len) diff --git a/Python/symtable.c b/Python/symtable.c index 579d047d526218..9b7b09dfe68ade 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2479,11 +2479,18 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, } else { if (st->st_cur->ste_annotation_block == NULL) { + PyObject *annotations_name = PyUnicode_FromFormat( + "", parent_ste->ste_name); + if (!annotations_name) { + VISIT_QUIT(st, 0); + } _Py_block_ty current_type = st->st_cur->ste_type; - if (!symtable_enter_block(st, parent_ste->ste_name, AnnotationBlock, + if (!symtable_enter_block(st, annotations_name, AnnotationBlock, key, LOCATION(annotation))) { + Py_DECREF(annotations_name); VISIT_QUIT(st, 0); } + Py_DECREF(annotations_name); parent_ste->ste_annotation_block = (struct _symtable_entry *)Py_NewRef(st->st_cur); if (current_type == ClassBlock) { @@ -2540,12 +2547,19 @@ static int symtable_visit_annotations(struct symtable *st, stmt_ty o, arguments_ty a, expr_ty returns, struct _symtable_entry *function_ste) { + PyObject *annotations_name = PyUnicode_FromFormat( + "", function_ste->ste_name); + if (!annotations_name) { + VISIT_QUIT(st, 0); + } int is_in_class = st->st_cur->ste_can_see_class_scope; _Py_block_ty current_type = st->st_cur->ste_type; - if (!symtable_enter_block(st, function_ste->ste_name, AnnotationBlock, + if (!symtable_enter_block(st, annotations_name, AnnotationBlock, (void *)a, LOCATION(o))) { + Py_DECREF(annotations_name); VISIT_QUIT(st, 0); } + Py_DECREF(annotations_name); if (is_in_class || current_type == ClassBlock) { st->st_cur->ste_can_see_class_scope = 1; if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(o))) { From f9d81b676a366ce6bccbcaeadac6cd1c85c91d8c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 21:57:55 -0400 Subject: [PATCH 26/61] fix test_pydoc --- Lib/test/test_pydoc/test_pydoc.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 436fdb38756ddd..b4489d77d19948 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -76,6 +76,11 @@ class A(builtins.object) | __weakref__%s class B(builtins.object) + | Methods defined here: + | + | __annotate__ = (...) + | + | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__%s @@ -86,8 +91,6 @@ class B(builtins.object) | Data and other attributes defined here: | | NO_MEANING = 'eggs' - | - | __annotations__ = {'NO_MEANING': } class C(builtins.object) | Methods defined here: @@ -175,6 +178,9 @@ class A(builtins.object) list of weak references to the object class B(builtins.object) + Methods defined here: + __annotate__ = (...) + ---------------------------------------------------------------------- Data descriptors defined here: __dict__ dictionary for instance variables @@ -183,7 +189,6 @@ class B(builtins.object) ---------------------------------------------------------------------- Data and other attributes defined here: NO_MEANING = 'eggs' - __annotations__ = {'NO_MEANING': } class C(builtins.object) From dd1f64a40051456491320b11cc464a038e73e76b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 22:02:05 -0400 Subject: [PATCH 27/61] fix test_traceback --- Lib/test/test_traceback.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 5987ec382e6c85..75a7c54ad73a72 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -622,6 +622,7 @@ def test_caret_in_type_annotation(self): def f_with_type(): def foo(a: THIS_DOES_NOT_EXIST ) -> int: return 0 + foo.__annotations__ lineno_f = f_with_type.__code__.co_firstlineno expected_f = ( @@ -629,7 +630,9 @@ def foo(a: THIS_DOES_NOT_EXIST ) -> int: f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' ' ~~~~~~~~^^\n' - f' File "{__file__}", line {lineno_f+1}, in f_with_type\n' + f' File "{__file__}", line {lineno_f+3}, in f_with_type\n' + ' foo.__annotations__\n' + f' File "{__file__}", line {lineno_f+1}, in \n' ' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n' ' ^^^^^^^^^^^^^^^^^^^\n' ) From 62f5b3ba3a30afb602752784e9cd3251a7afc21f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 May 2024 22:04:23 -0400 Subject: [PATCH 28/61] fix test_opcodes --- Lib/test/test_opcodes.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_opcodes.py b/Lib/test/test_opcodes.py index 72488b2bb6b4ff..f7cc8331b8d844 100644 --- a/Lib/test/test_opcodes.py +++ b/Lib/test/test_opcodes.py @@ -39,16 +39,19 @@ class C: pass def test_use_existing_annotations(self): ns = {'__annotations__': {1: 2}} exec('x: int', ns) - self.assertEqual(ns['__annotations__'], {'x': int, 1: 2}) + self.assertEqual(ns['__annotations__'], {1: 2}) def test_do_not_recreate_annotations(self): # Don't rely on the existence of the '__annotations__' global. with support.swap_item(globals(), '__annotations__', {}): - del globals()['__annotations__'] + globals().pop('__annotations__', None) class C: - del __annotations__ - with self.assertRaises(NameError): - x: int + try: + del __annotations__ + except NameError: + pass + x: int + self.assertEqual(C.__annotations__, {"x": int}) def test_raise_class_exceptions(self): From 1a63f5d2582d7468a2eccde4c0fd01048e165c28 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 22 May 2024 05:29:05 -0700 Subject: [PATCH 29/61] Raise NotImplementedError --- Lib/test/test_type_annotations.py | 6 +++--- Python/compile.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 6b39ad771677e5..84bec8e0afc426 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -315,7 +315,7 @@ class X: def test_module(self): ns = run_code("x: undefined = 1") anno = ns["__annotate__"] - with self.assertRaises(AssertionError): # TODO NotImplementedError + with self.assertRaises(NotImplementedError): anno(2) with self.assertRaises(NameError): @@ -350,8 +350,8 @@ class X: annotate = obj.__annotate__ self.assertIsInstance(annotate, types.FunctionType) self.assertEqual(annotate.__name__, f"") - with self.assertRaises(AssertionError): # TODO NotImplementedError + with self.assertRaises(NotImplementedError): annotate(inspect.FORWARDREF) - with self.assertRaises(AssertionError): # TODO NotImplementedError + with self.assertRaises(NotImplementedError): annotate(None) self.assertEqual(annotate(inspect.VALUE), {"x": int}) diff --git a/Python/compile.c b/Python/compile.c index e1807fedd8054f..381f3eb4b2a486 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1658,7 +1658,7 @@ compiler_leave_annotations_scope(struct compiler *c, location loc, ADDOP_I(c, loc, BUILD_MAP, annotations_len); ADDOP_IN_SCOPE(c, loc, RETURN_VALUE); USE_LABEL(c, label); - ADDOP_IN_SCOPE(c, loc, LOAD_ASSERTION_ERROR); + ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_NOTIMPLEMENTEDERROR); ADDOP_I(c, loc, RAISE_VARARGS, 1); PyCodeObject *co = optimize_and_assemble(c, 1); compiler_exit_scope(c); From a0c39b5770acd773ee359dcb79643b4bf1e0aca2 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 22 May 2024 06:22:50 -0700 Subject: [PATCH 30/61] blurb --- .../2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst new file mode 100644 index 00000000000000..dfb3958535a16b --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst @@ -0,0 +1 @@ +Evaluation of annotations is now deferred. See PEP 649 for details. From 083bbc58b3340da82eb57d54ddc6050d84d40cd6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 22 May 2024 06:27:51 -0700 Subject: [PATCH 31/61] Fix test_dis --- Lib/test/test_dis.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 67a630e1346109..b0873a4f9381b7 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -352,32 +352,21 @@ def wrap_func_w_kwargs(): dis_annot_stmt_str = """\ 0 RESUME 0 - 2 SETUP_ANNOTATIONS - LOAD_CONST 0 (1) + 2 LOAD_CONST 0 (1) STORE_NAME 0 (x) - LOAD_NAME 1 (int) - LOAD_NAME 2 (__annotations__) - LOAD_CONST 1 ('x') - STORE_SUBSCR - - 3 LOAD_NAME 3 (fun) - PUSH_NULL - LOAD_CONST 0 (1) - CALL 1 - LOAD_NAME 2 (__annotations__) - LOAD_CONST 2 ('y') - STORE_SUBSCR 4 LOAD_CONST 0 (1) - LOAD_NAME 4 (lst) - LOAD_NAME 3 (fun) + LOAD_NAME 1 (lst) + LOAD_NAME 2 (fun) PUSH_NULL - LOAD_CONST 3 (0) + LOAD_CONST 1 (0) CALL 1 STORE_SUBSCR - LOAD_NAME 1 (int) - POP_TOP - RETURN_CONST 4 (None) + + 2 LOAD_CONST 2 ( at 0x..., file "", line 2>) + MAKE_FUNCTION + STORE_NAME 3 (__annotate__) + RETURN_CONST 3 (None) """ compound_stmt_str = """\ From de1b23569f9b875dee4a7da4d688e4a88e70e1c7 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 22 May 2024 06:28:41 -0700 Subject: [PATCH 32/61] Remove broken tests --- Lib/test/test_grammar.py | 58 ---------------------------------------- 1 file changed, 58 deletions(-) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index 388bc72fe2385e..aa10efb892b953 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -306,16 +306,6 @@ def test_eof_error(self): var_annot_global: int # a global annotated is necessary for test_var_annot -# custom namespace for testing __annotations__ - -class CNS: - def __init__(self): - self._dct = {} - def __setitem__(self, item, value): - self._dct[item.lower()] = value - def __getitem__(self, item): - return self._dct[item] - class GrammarTests(unittest.TestCase): @@ -446,16 +436,6 @@ class F(C, A): self.assertEqual(E.__annotations__, {}) self.assertEqual(F.__annotations__, {}) - - def test_var_annot_metaclass_semantics(self): - class CMeta(type): - @classmethod - def __prepare__(metacls, name, bases, **kwds): - return {'__annotations__': CNS()} - class CC(metaclass=CMeta): - XX: 'ANNOT' - self.assertEqual(CC.__annotations__['xx'], 'ANNOT') - def test_var_annot_module_semantics(self): self.assertEqual(test.__annotations__, {}) self.assertEqual(ann_module.__annotations__, @@ -483,44 +463,6 @@ def test_var_annot_simple_exec(self): with self.assertRaises(KeyError): gns['__annotate__'] - def test_var_annot_custom_maps(self): - # tests with custom locals() and __annotations__ - ns = {'__annotations__': CNS()} - exec('X: int; Z: str = "Z"; (w): complex = 1j', ns) - self.assertEqual(ns['__annotations__']['x'], int) - self.assertEqual(ns['__annotations__']['z'], str) - with self.assertRaises(KeyError): - ns['__annotations__']['w'] - nonloc_ns = {} - class CNS2: - def __init__(self): - self._dct = {} - def __setitem__(self, item, value): - nonlocal nonloc_ns - self._dct[item] = value - nonloc_ns[item] = value - def __getitem__(self, item): - return self._dct[item] - exec('x: int = 1', {}, CNS2()) - self.assertEqual(nonloc_ns['__annotations__']['x'], int) - - def test_var_annot_refleak(self): - # complex case: custom locals plus custom __annotations__ - # this was causing refleak - cns = CNS() - nonloc_ns = {'__annotations__': cns} - class CNS2: - def __init__(self): - self._dct = {'__annotations__': cns} - def __setitem__(self, item, value): - nonlocal nonloc_ns - self._dct[item] = value - nonloc_ns[item] = value - def __getitem__(self, item): - return self._dct[item] - exec('X: str', {}, CNS2()) - self.assertEqual(nonloc_ns['__annotations__']['x'], str) - def test_var_annot_rhs(self): ns = {} exec('x: tuple = 1, 2', ns) From 5f5cf115122815e85b1b7d6528c6898231aedaa5 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 May 2024 22:08:53 -0700 Subject: [PATCH 33/61] No deferred evaluation in interactive mode --- Include/internal/pycore_symtable.h | 1 + Python/compile.c | 39 ++++++++++++++++++++++++++---- Python/symtable.c | 8 ++++-- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index f109a7229c35df..b3eb2e12ddd2c9 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -72,6 +72,7 @@ struct symtable { the symbol table */ int recursion_depth; /* current recursion depth */ int recursion_limit; /* recursion limit */ + int st_kind; /* kind of module */ }; typedef struct _symtable_entry { diff --git a/Python/compile.c b/Python/compile.c index 381f3eb4b2a486..f888aba177e7d8 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1653,7 +1653,7 @@ compiler_setup_annotations_scope(struct compiler *c, location loc, static int compiler_leave_annotations_scope(struct compiler *c, location loc, - int annotations_len, jump_target_label label) + Py_ssize_t annotations_len, jump_target_label label) { ADDOP_I(c, loc, BUILD_MAP, annotations_len); ADDOP_IN_SCOPE(c, loc, RETURN_VALUE); @@ -6595,6 +6595,23 @@ check_ann_expr(struct compiler *c, expr_ty e) return SUCCESS; } +static int +check_annotation(struct compiler *c, stmt_ty s) +{ + /* Annotations of complex targets does not produce anything + under annotations future */ + if (c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) { + return SUCCESS; + } + + /* Annotations are only evaluated in a module or class. */ + if (c->u->u_scope_type == COMPILER_SCOPE_MODULE || + c->u->u_scope_type == COMPILER_SCOPE_CLASS) { + return check_ann_expr(c, s->v.AnnAssign.annotation); + } + return SUCCESS; +} + static int check_ann_subscr(struct compiler *c, expr_ty e) { @@ -6630,7 +6647,11 @@ compiler_annassign(struct compiler *c, stmt_ty s) { location loc = LOC(s); expr_ty targ = s->v.AnnAssign.target; - PyObject* mangled; + bool is_interactive = ( + c->c_st->st_kind == Interactive_kind && c->u->u_scope_type == COMPILER_SCOPE_MODULE + ); + bool future_annotations = c->c_future.ff_features & CO_FUTURE_ANNOTATIONS; + PyObject *mangled; assert(s->kind == AnnAssign_kind); @@ -6645,11 +6666,16 @@ compiler_annassign(struct compiler *c, stmt_ty s) return ERROR; } /* If we have a simple name in a module or class, store annotation. */ - if ((c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && + if ((future_annotations || is_interactive) && s->v.AnnAssign.simple && (c->u->u_scope_type == COMPILER_SCOPE_MODULE || c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { - VISIT(c, annexpr, s->v.AnnAssign.annotation); + if (future_annotations) { + VISIT(c, annexpr, s->v.AnnAssign.annotation); + } + else { + VISIT(c, expr, s->v.AnnAssign.annotation); + } ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id); ADDOP_LOAD_CONST_NEW(c, loc, mangled); @@ -6678,7 +6704,10 @@ compiler_annassign(struct compiler *c, stmt_ty s) targ->kind); return ERROR; } - /* For non-simple AnnAssign, the annotation is not evaluated. */ + /* Annotation is evaluated last. */ + if ((future_annotations || is_interactive) && !s->v.AnnAssign.simple && check_annotation(c, s) < 0) { + return ERROR; + } return SUCCESS; } diff --git a/Python/symtable.c b/Python/symtable.c index 9b7b09dfe68ade..a10e77e5738a9b 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -427,6 +427,7 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, _PyFutureFeatures *future) } st->st_top = st->st_cur; + st->st_kind = mod->kind; switch (mod->kind) { case Module_kind: seq = mod->v.Module.body; @@ -2471,13 +2472,16 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, struct _symtable_entry *parent_ste, void *key) { int future_annotations = st->st_future->ff_features & CO_FUTURE_ANNOTATIONS; + int is_top_level_interactive = ( + st->st_kind == Interactive_kind && st->st_cur->ste_type == ModuleBlock + ); if (future_annotations) { if(!symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, key, LOCATION(annotation))) { VISIT_QUIT(st, 0); } } - else { + else if (!is_top_level_interactive) { if (st->st_cur->ste_annotation_block == NULL) { PyObject *annotations_name = PyUnicode_FromFormat( "", parent_ste->ste_name); @@ -2518,7 +2522,7 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, } } VISIT(st, expr, annotation); - if (!symtable_exit_block(st)) { + if ((future_annotations || !is_top_level_interactive) && !symtable_exit_block(st)) { VISIT_QUIT(st, 0); } return 1; From 77f3b1c524b80fd5a87ab748acd4f65a1f7b6f18 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 May 2024 22:30:20 -0700 Subject: [PATCH 34/61] gh-119443: Turn off from __future__ import annotations in REPL --- Lib/_pyrepl/simple_interact.py | 2 +- Lib/test/test_pyrepl/test_interact.py | 9 +++++++++ .../2024-05-23-22-29-59.gh-issue-119443.KAGz6S.rst | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-05-23-22-29-59.gh-issue-119443.KAGz6S.rst diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py index 8ab4dab757685e..3dfb1d7ad736e3 100644 --- a/Lib/_pyrepl/simple_interact.py +++ b/Lib/_pyrepl/simple_interact.py @@ -95,7 +95,7 @@ def runsource(self, source, filename="", symbol="single"): the_symbol = symbol if stmt is last_stmt else "exec" item = wrapper([stmt]) try: - code = compile(item, filename, the_symbol) + code = compile(item, filename, the_symbol, dont_inherit=True) except (OverflowError, ValueError): self.showsyntaxerror(filename) return False diff --git a/Lib/test/test_pyrepl/test_interact.py b/Lib/test/test_pyrepl/test_interact.py index 10e34045bcf92d..6ebd51fe14dd62 100644 --- a/Lib/test/test_pyrepl/test_interact.py +++ b/Lib/test/test_pyrepl/test_interact.py @@ -94,3 +94,12 @@ def test_runsource_shows_syntax_error_for_failed_compilation(self): with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror: console.runsource(source) mock_showsyntaxerror.assert_called_once() + + def test_no_active_future(self): + console = InteractiveColoredConsole() + source = "x: int = 1; print(__annotations__)" + f = io.StringIO() + with contextlib.redirect_stdout(f): + result = console.runsource(source) + self.assertFalse(result) + self.assertEqual(f.getvalue(), "{'x': }\n") diff --git a/Misc/NEWS.d/next/Library/2024-05-23-22-29-59.gh-issue-119443.KAGz6S.rst b/Misc/NEWS.d/next/Library/2024-05-23-22-29-59.gh-issue-119443.KAGz6S.rst new file mode 100644 index 00000000000000..4470c566a37d88 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-05-23-22-29-59.gh-issue-119443.KAGz6S.rst @@ -0,0 +1,2 @@ +The interactive REPL no longer runs with ``from __future__ import +annotations`` enabled. Patch by Jelle Zijlstra. From 421783051464ff2ef7d38bac668f1925e7e6c578 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 May 2024 09:03:12 -0700 Subject: [PATCH 35/61] Fix refleak --- Python/compile.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index f888aba177e7d8..3737eb3aecf3f6 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -2131,20 +2131,23 @@ compiler_visit_annotations(struct compiler *c, location loc, Py_ssize_t annotations_len = 0; int future_annotations = c->c_future.ff_features & CO_FUTURE_ANNOTATIONS; + NEW_JUMP_TARGET_LABEL(c, raise_notimp); + PySTEntryObject *ste; int result = _PySymtable_LookupOptional(c->c_st, args, &ste); if (result == -1) { return ERROR; } assert(ste != NULL); - NEW_JUMP_TARGET_LABEL(c, raise_notimp); if (!future_annotations && ste->ste_annotations_used) { - RETURN_IF_ERROR( - compiler_setup_annotations_scope(c, loc, (void *)args, raise_notimp, - ste->ste_name) - ); + if (compiler_setup_annotations_scope(c, loc, (void *)args, raise_notimp, + ste->ste_name) < 0) { + Py_DECREF(ste); + return ERROR; + } } + Py_DECREF(ste); RETURN_IF_ERROR( compiler_visit_argannotations(c, args->args, &annotations_len, loc)); From 13f5d761743b4bc41890e8c62aec38b548eecfd6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 May 2024 09:09:31 -0700 Subject: [PATCH 36/61] Fix another refleak --- Python/symtable.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Python/symtable.c b/Python/symtable.c index a10e77e5738a9b..1c655167a13948 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1684,8 +1684,10 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) } if (!symtable_visit_annotations(st, s, s->v.FunctionDef.args, - s->v.FunctionDef.returns, new_ste)) + s->v.FunctionDef.returns, new_ste)) { + Py_DECREF(new_ste); VISIT_QUIT(st, 0); + } if (!symtable_enter_existing_block(st, new_ste)) { Py_DECREF(new_ste); VISIT_QUIT(st, 0); From 242301c3b6fe04b1353d1e456d01b559aa687258 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 May 2024 09:15:51 -0700 Subject: [PATCH 37/61] Exit scope correctly on error --- Python/compile.c | 65 +++++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 3737eb3aecf3f6..75eef62b587424 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -2119,6 +2119,38 @@ compiler_visit_argannotations(struct compiler *c, asdl_arg_seq* args, return SUCCESS; } +static int +compiler_visit_annotations_in_scope(struct compiler *c, location loc, + arguments_ty args, expr_ty returns, + Py_ssize_t *annotations_len) +{ + RETURN_IF_ERROR( + compiler_visit_argannotations(c, args->args, annotations_len, loc)); + + RETURN_IF_ERROR( + compiler_visit_argannotations(c, args->posonlyargs, annotations_len, loc)); + + if (args->vararg && args->vararg->annotation) { + RETURN_IF_ERROR( + compiler_visit_argannotation(c, args->vararg->arg, + args->vararg->annotation, annotations_len, loc)); + } + + RETURN_IF_ERROR( + compiler_visit_argannotations(c, args->kwonlyargs, annotations_len, loc)); + + if (args->kwarg && args->kwarg->annotation) { + RETURN_IF_ERROR( + compiler_visit_argannotation(c, args->kwarg->arg, + args->kwarg->annotation, annotations_len, loc)); + } + + RETURN_IF_ERROR( + compiler_visit_argannotation(c, &_Py_ID(return), returns, annotations_len, loc)); + + return 0; +} + static int compiler_visit_annotations(struct compiler *c, location loc, arguments_ty args, expr_ty returns) @@ -2139,8 +2171,9 @@ compiler_visit_annotations(struct compiler *c, location loc, return ERROR; } assert(ste != NULL); + bool annotations_used = ste->ste_annotations_used; - if (!future_annotations && ste->ste_annotations_used) { + if (!future_annotations && annotations_used) { if (compiler_setup_annotations_scope(c, loc, (void *)args, raise_notimp, ste->ste_name) < 0) { Py_DECREF(ste); @@ -2149,30 +2182,13 @@ compiler_visit_annotations(struct compiler *c, location loc, } Py_DECREF(ste); - RETURN_IF_ERROR( - compiler_visit_argannotations(c, args->args, &annotations_len, loc)); - - RETURN_IF_ERROR( - compiler_visit_argannotations(c, args->posonlyargs, &annotations_len, loc)); - - if (args->vararg && args->vararg->annotation) { - RETURN_IF_ERROR( - compiler_visit_argannotation(c, args->vararg->arg, - args->vararg->annotation, &annotations_len, loc)); - } - - RETURN_IF_ERROR( - compiler_visit_argannotations(c, args->kwonlyargs, &annotations_len, loc)); - - if (args->kwarg && args->kwarg->annotation) { - RETURN_IF_ERROR( - compiler_visit_argannotation(c, args->kwarg->arg, - args->kwarg->annotation, &annotations_len, loc)); + if (compiler_visit_annotations_in_scope(c, loc, args, returns, &annotations_len) < 0) { + if (!future_annotations && annotations_used) { + compiler_exit_scope(c); + } + return ERROR; } - RETURN_IF_ERROR( - compiler_visit_argannotation(c, &_Py_ID(return), returns, &annotations_len, loc)); - if (future_annotations) { if (annotations_len) { ADDOP_I(c, loc, BUILD_TUPLE, annotations_len * 2); @@ -2180,8 +2196,7 @@ compiler_visit_annotations(struct compiler *c, location loc, } } else { - assert(ste != NULL); - if (ste->ste_annotations_used) { + if (annotations_used) { RETURN_IF_ERROR( compiler_leave_annotations_scope(c, loc, annotations_len, raise_notimp) ); From 239ba23c66f03aa996f9569a57d6f05327aead84 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 May 2024 09:41:49 -0700 Subject: [PATCH 38/61] fix test --- Lib/test/test_pyrepl/test_interact.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pyrepl/test_interact.py b/Lib/test/test_pyrepl/test_interact.py index 6ebd51fe14dd62..976915591110f5 100644 --- a/Lib/test/test_pyrepl/test_interact.py +++ b/Lib/test/test_pyrepl/test_interact.py @@ -97,7 +97,7 @@ def test_runsource_shows_syntax_error_for_failed_compilation(self): def test_no_active_future(self): console = InteractiveColoredConsole() - source = "x: int = 1; print(__annotations__)" + source = "if True:\n x: int = 1; print(__annotations__)" f = io.StringIO() with contextlib.redirect_stdout(f): result = console.runsource(source) From b62e04c4c7808260f4442da1039974a54f49cacb Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 May 2024 17:39:22 -0700 Subject: [PATCH 39/61] Initialize field --- Python/compile.c | 4 ++-- Python/symtable.c | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 75eef62b587424..ecee0043252af4 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -2173,7 +2173,7 @@ compiler_visit_annotations(struct compiler *c, location loc, assert(ste != NULL); bool annotations_used = ste->ste_annotations_used; - if (!future_annotations && annotations_used) { + if (annotations_used && !future_annotations) { if (compiler_setup_annotations_scope(c, loc, (void *)args, raise_notimp, ste->ste_name) < 0) { Py_DECREF(ste); @@ -2183,7 +2183,7 @@ compiler_visit_annotations(struct compiler *c, location loc, Py_DECREF(ste); if (compiler_visit_annotations_in_scope(c, loc, args, returns, &annotations_len) < 0) { - if (!future_annotations && annotations_used) { + if (annotations_used && !future_annotations) { compiler_exit_scope(c); } return ERROR; diff --git a/Python/symtable.c b/Python/symtable.c index 1c655167a13948..b7660b6a4a369e 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -111,6 +111,7 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block, ste->ste_varkeywords = 0; ste->ste_opt_lineno = 0; ste->ste_opt_col_offset = 0; + ste->ste_annotations_used = 0; ste->ste_lineno = lineno; ste->ste_col_offset = col_offset; ste->ste_end_lineno = end_lineno; From 5ae206dfa04aa8df525dd10284705b387c4c493f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 May 2024 18:02:23 -0700 Subject: [PATCH 40/61] self-review --- Include/cpython/code.h | 2 +- Objects/typeobject.c | 1 - Python/compile.c | 13 ++++++++++--- Python/symtable.c | 3 ++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Include/cpython/code.h b/Include/cpython/code.h index d64f79bceaa6c7..ef8f9304ccab56 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -219,7 +219,7 @@ struct PyCodeObject _PyCode_DEF(1); #define CO_FUTURE_GENERATOR_STOP 0x800000 #define CO_FUTURE_ANNOTATIONS 0x1000000 -#define CO_NO_MONITORING_EVENTS 0x4000000 +#define CO_NO_MONITORING_EVENTS 0x2000000 /* This should be defined if a future statement modifies the syntax. For example, when a keyword is added. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index f03508e5fb6a05..11f9c570ac4971 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1829,7 +1829,6 @@ type_set_annotations(PyTypeObject *type, PyObject *value, void *context) return -1; } } - PyType_Modified(type); if (result < 0) { Py_DECREF(dict); return -1; diff --git a/Python/compile.c b/Python/compile.c index ecee0043252af4..4987f8fe25dcc5 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1643,6 +1643,8 @@ compiler_setup_annotations_scope(struct compiler *c, location loc, return ERROR; } c->u->u_metadata.u_posonlyargcount = 1; + // if .format != 1: raise NotImplementedError + // The raise is at the end of the function so there are no jumps in the happy path. _Py_DECLARE_STR(format, ".format"); ADDOP_I(c, loc, LOAD_FAST, 0); ADDOP_LOAD_CONST(c, loc, _PyLong_GetOne()); @@ -1681,7 +1683,7 @@ compiler_collect_annotations(struct compiler *c, asdl_stmt_seq *stmts, stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, i); switch (st->kind) { case AnnAssign_kind: - // Only "simple" names (i.e., unparenthesized names) are stored. + // Only "simple" (i.e., unparenthesized) names are stored. if (st->v.AnnAssign.simple) { PyObject *mangled = _Py_Mangle(c->u->u_private, st->v.AnnAssign.target->v.Name.id); ADDOP_LOAD_CONST_NEW(c, LOC(st), mangled); @@ -1761,7 +1763,9 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, 0); loc = LOC(st); } - /* Every annotated class and module should have __annotations__. */ + /* If from __future__ import annotations is active, + * every annotated class and module should have __annotations__. + * Else __annotate__ is created when necessary. */ if ((c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && find_ann(stmts)) { ADDOP(c, loc, SETUP_ANNOTATIONS); } @@ -1789,6 +1793,9 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(stmts); i++) { VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i)); } + // If there are annotations and the future import is not on, we + // collect the annotations in a separate pass and generate an + // __annotate__ function. See PEP 649. if (!(c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && c->u->u_ste->ste_annotation_block != NULL) { NEW_JUMP_TARGET_LABEL(c, raise_notimp); @@ -2156,7 +2163,7 @@ compiler_visit_annotations(struct compiler *c, location loc, arguments_ty args, expr_ty returns) { /* Push arg annotation names and values. - The expressions are evaluated out-of-order wrt the source code. + The expressions are evaluated separately from the rest of the source code. Return -1 on error, or a combination of flags to add to the function. */ diff --git a/Python/symtable.c b/Python/symtable.c index b7660b6a4a369e..c9d2f800ed9c54 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2508,7 +2508,8 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, } _Py_DECLARE_STR(format, ".format"); - // We need to insert code that reads this "parameter" to the function. + // The generated __annotate__ function takes a single parameter with the + // internal name ".format". if (!symtable_add_def(st, &_Py_STR(format), DEF_PARAM, LOCATION(annotation))) { return 0; From 24fd3284ba93b8b0b9e6d55989617d8cfdfdfb8c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 May 2024 18:26:10 -0700 Subject: [PATCH 41/61] Fix crash found by CIFuzz --- Lib/test/test_type_annotations.py | 5 +++++ Python/compile.c | 28 ++++++++++++---------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 84bec8e0afc426..a40dfd5f5069ed 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -355,3 +355,8 @@ class X: with self.assertRaises(NotImplementedError): annotate(None) self.assertEqual(annotate(inspect.VALUE), {"x": int}) + + def test_comprehension_in_annotation(self): + # This crashed in an earlier version of the code + ns = run_code("x: [y for y in range(10)]") + self.assertEqual(ns["__annotate__"](1), {"x": list(range(10))}) diff --git a/Python/compile.c b/Python/compile.c index 4987f8fe25dcc5..9b0b3bfd6cebd9 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1635,8 +1635,7 @@ compiler_unwind_fblock_stack(struct compiler *c, location *ploc, static int compiler_setup_annotations_scope(struct compiler *c, location loc, - void *key, jump_target_label label, - PyObject *name) + void *key, PyObject *name) { if (compiler_enter_scope(c, name, COMPILER_SCOPE_ANNOTATIONS, key, loc.lineno) == -1) { @@ -1644,24 +1643,24 @@ compiler_setup_annotations_scope(struct compiler *c, location loc, } c->u->u_metadata.u_posonlyargcount = 1; // if .format != 1: raise NotImplementedError - // The raise is at the end of the function so there are no jumps in the happy path. _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_EQ << 5) | compare_masks[Py_EQ]); - ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, label); + 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_COMMON_CONSTANT, CONSTANT_NOTIMPLEMENTEDERROR); + ADDOP_I(c, loc, RAISE_VARARGS, 1); + USE_LABEL(c, body); return 0; } static int compiler_leave_annotations_scope(struct compiler *c, location loc, - Py_ssize_t annotations_len, jump_target_label label) + Py_ssize_t annotations_len) { ADDOP_I(c, loc, BUILD_MAP, annotations_len); ADDOP_IN_SCOPE(c, loc, RETURN_VALUE); - USE_LABEL(c, label); - ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_NOTIMPLEMENTEDERROR); - ADDOP_I(c, loc, RAISE_VARARGS, 1); PyCodeObject *co = optimize_and_assemble(c, 1); compiler_exit_scope(c); if (co == NULL) { @@ -1798,16 +1797,15 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) // __annotate__ function. See PEP 649. if (!(c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && c->u->u_ste->ste_annotation_block != NULL) { - NEW_JUMP_TARGET_LABEL(c, raise_notimp); void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); - RETURN_IF_ERROR(compiler_setup_annotations_scope(c, loc, key, raise_notimp, + RETURN_IF_ERROR(compiler_setup_annotations_scope(c, loc, key, c->u->u_ste->ste_annotation_block->ste_name)); int annotations_len = 0; RETURN_IF_ERROR( compiler_collect_annotations(c, stmts, &annotations_len) ); RETURN_IF_ERROR( - compiler_leave_annotations_scope(c, loc, annotations_len, raise_notimp) + compiler_leave_annotations_scope(c, loc, annotations_len) ); RETURN_IF_ERROR( compiler_nameop(c, loc, &_Py_ID(__annotate__), Store) @@ -2170,8 +2168,6 @@ compiler_visit_annotations(struct compiler *c, location loc, Py_ssize_t annotations_len = 0; int future_annotations = c->c_future.ff_features & CO_FUTURE_ANNOTATIONS; - NEW_JUMP_TARGET_LABEL(c, raise_notimp); - PySTEntryObject *ste; int result = _PySymtable_LookupOptional(c->c_st, args, &ste); if (result == -1) { @@ -2181,7 +2177,7 @@ compiler_visit_annotations(struct compiler *c, location loc, bool annotations_used = ste->ste_annotations_used; if (annotations_used && !future_annotations) { - if (compiler_setup_annotations_scope(c, loc, (void *)args, raise_notimp, + if (compiler_setup_annotations_scope(c, loc, (void *)args, ste->ste_name) < 0) { Py_DECREF(ste); return ERROR; @@ -2205,7 +2201,7 @@ compiler_visit_annotations(struct compiler *c, location loc, else { if (annotations_used) { RETURN_IF_ERROR( - compiler_leave_annotations_scope(c, loc, annotations_len, raise_notimp) + compiler_leave_annotations_scope(c, loc, annotations_len) ); return MAKE_FUNCTION_ANNOTATE; } From c18186499017e7efbe875bd1db4993dbb1f323ca Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 May 2024 17:10:04 -0700 Subject: [PATCH 42/61] fix merge --- Python/symtable.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/symtable.c b/Python/symtable.c index 80dbd29541a37c..9d248d52d8de2a 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1356,7 +1356,7 @@ symtable_enter_existing_block(struct symtable *st, PySTEntryObject* ste) } /* No need to inherit ste_mangled_names in classes, where all names * are mangled. */ - if (prev && prev->ste_mangled_names != NULL && block != ClassBlock) { + if (prev && prev->ste_mangled_names != NULL && ste->ste_type != ClassBlock) { ste->ste_mangled_names = Py_NewRef(prev->ste_mangled_names); } /* The entry is owned by the stack. Borrow it for st_cur. */ From 0befff50694769b7abd54104fe54c3c05fd63135 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 May 2024 17:17:34 -0700 Subject: [PATCH 43/61] Name the function as __annotate__ --- Python/symtable.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/Python/symtable.c b/Python/symtable.c index 9d248d52d8de2a..1943f725802277 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2501,18 +2501,11 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, } else if (!is_top_level_interactive) { if (st->st_cur->ste_annotation_block == NULL) { - PyObject *annotations_name = PyUnicode_FromFormat( - "", parent_ste->ste_name); - if (!annotations_name) { - VISIT_QUIT(st, 0); - } _Py_block_ty current_type = st->st_cur->ste_type; - if (!symtable_enter_block(st, annotations_name, AnnotationBlock, + if (!symtable_enter_block(st, &_Py_ID(__annotate__), AnnotationBlock, key, LOCATION(annotation))) { - Py_DECREF(annotations_name); VISIT_QUIT(st, 0); } - Py_DECREF(annotations_name); parent_ste->ste_annotation_block = (struct _symtable_entry *)Py_NewRef(st->st_cur); if (current_type == ClassBlock) { @@ -2570,19 +2563,12 @@ static int symtable_visit_annotations(struct symtable *st, stmt_ty o, arguments_ty a, expr_ty returns, struct _symtable_entry *function_ste) { - PyObject *annotations_name = PyUnicode_FromFormat( - "", function_ste->ste_name); - if (!annotations_name) { - VISIT_QUIT(st, 0); - } int is_in_class = st->st_cur->ste_can_see_class_scope; _Py_block_ty current_type = st->st_cur->ste_type; - if (!symtable_enter_block(st, annotations_name, AnnotationBlock, + if (!symtable_enter_block(st, &_Py_ID(__annotate__), AnnotationBlock, (void *)a, LOCATION(o))) { - Py_DECREF(annotations_name); VISIT_QUIT(st, 0); } - Py_DECREF(annotations_name); if (is_in_class || current_type == ClassBlock) { st->st_cur->ste_can_see_class_scope = 1; if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(o))) { From ae7714cb338b8c1244b94ef028a111f6046d2fda Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 May 2024 17:41:18 -0700 Subject: [PATCH 44/61] Replace find_ann() --- Python/compile.c | 83 ++--------------------------------------------- Python/symtable.c | 1 + 2 files changed, 3 insertions(+), 81 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 50cd88f8d93655..81f63f6cd52eed 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1332,85 +1332,6 @@ compiler_exit_scope(struct compiler *c) PyErr_SetRaisedException(exc); } -/* Search if variable annotations are present statically in a block. */ - -static bool -find_ann(asdl_stmt_seq *stmts) -{ - int i, j, res = 0; - stmt_ty st; - - for (i = 0; i < asdl_seq_LEN(stmts); i++) { - st = (stmt_ty)asdl_seq_GET(stmts, i); - switch (st->kind) { - case AnnAssign_kind: - return true; - case For_kind: - res = find_ann(st->v.For.body) || - find_ann(st->v.For.orelse); - break; - case AsyncFor_kind: - res = find_ann(st->v.AsyncFor.body) || - find_ann(st->v.AsyncFor.orelse); - break; - case While_kind: - res = find_ann(st->v.While.body) || - find_ann(st->v.While.orelse); - break; - case If_kind: - res = find_ann(st->v.If.body) || - find_ann(st->v.If.orelse); - break; - case With_kind: - res = find_ann(st->v.With.body); - break; - case AsyncWith_kind: - res = find_ann(st->v.AsyncWith.body); - break; - case Try_kind: - for (j = 0; j < asdl_seq_LEN(st->v.Try.handlers); j++) { - excepthandler_ty handler = (excepthandler_ty)asdl_seq_GET( - st->v.Try.handlers, j); - if (find_ann(handler->v.ExceptHandler.body)) { - return true; - } - } - res = find_ann(st->v.Try.body) || - find_ann(st->v.Try.finalbody) || - find_ann(st->v.Try.orelse); - break; - case TryStar_kind: - for (j = 0; j < asdl_seq_LEN(st->v.TryStar.handlers); j++) { - excepthandler_ty handler = (excepthandler_ty)asdl_seq_GET( - st->v.TryStar.handlers, j); - if (find_ann(handler->v.ExceptHandler.body)) { - return true; - } - } - res = find_ann(st->v.TryStar.body) || - find_ann(st->v.TryStar.finalbody) || - find_ann(st->v.TryStar.orelse); - break; - case Match_kind: - for (j = 0; j < asdl_seq_LEN(st->v.Match.cases); j++) { - match_case_ty match_case = (match_case_ty)asdl_seq_GET( - st->v.Match.cases, j); - if (find_ann(match_case->body)) { - return true; - } - } - break; - default: - res = false; - break; - } - if (res) { - break; - } - } - return res; -} - /* * Frame block handling functions */ @@ -1757,7 +1678,7 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) /* If from __future__ import annotations is active, * every annotated class and module should have __annotations__. * Else __annotate__ is created when necessary. */ - if ((c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && find_ann(stmts)) { + if ((c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && c->u->u_ste->ste_annotations_used) { ADDOP(c, loc, SETUP_ANNOTATIONS); } if (!asdl_seq_LEN(stmts)) { @@ -1817,7 +1738,7 @@ compiler_codegen(struct compiler *c, mod_ty mod) } break; case Interactive_kind: - if (find_ann(mod->v.Interactive.body)) { + if (c->u->u_ste->ste_annotations_used) { ADDOP(c, loc, SETUP_ANNOTATIONS); } c->c_interactive = 1; diff --git a/Python/symtable.c b/Python/symtable.c index 1943f725802277..7314eb91fb44ee 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1809,6 +1809,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) VISIT(st, expr, s->v.Assign.value); break; case AnnAssign_kind: + st->st_cur->ste_annotations_used = 1; if (s->v.AnnAssign.target->kind == Name_kind) { expr_ty e_name = s->v.AnnAssign.target; long cur = symtable_lookup(st, e_name->v.Name.id); From 431811a3a4ec9892b3e6eea45ab21855b8cb2d8a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 May 2024 17:41:48 -0700 Subject: [PATCH 45/61] fix test --- Lib/test/test_type_annotations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index a40dfd5f5069ed..205802a1f7b6e2 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -349,7 +349,7 @@ class X: with self.subTest(obj=obj): annotate = obj.__annotate__ self.assertIsInstance(annotate, types.FunctionType) - self.assertEqual(annotate.__name__, f"") + self.assertEqual(annotate.__name__, "__annotate__") with self.assertRaises(NotImplementedError): annotate(inspect.FORWARDREF) with self.assertRaises(NotImplementedError): From 7ca24d3b40da6258b5b884e8b580d05624bdbdc9 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 May 2024 17:59:56 -0700 Subject: [PATCH 46/61] Remove second compiler pass --- Python/compile.c | 132 +++++++++++++++++------------------------------ 1 file changed, 46 insertions(+), 86 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 81f63f6cd52eed..45ca6b8995c52c 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -258,6 +258,7 @@ struct compiler_unit { PyObject *u_private; /* for private name mangling */ PyObject *u_static_attributes; /* for class: attributes accessed via self.X */ + PyObject *u_deferred_annotations; /* AnnAssign nodes deferred to the end of compilation */ instr_sequence *u_instr_sequence; /* codegen output */ @@ -597,6 +598,7 @@ compiler_unit_free(struct compiler_unit *u) Py_CLEAR(u->u_metadata.u_fasthidden); Py_CLEAR(u->u_private); Py_CLEAR(u->u_static_attributes); + Py_CLEAR(u->u_deferred_annotations); PyMem_Free(u); } @@ -1251,6 +1253,7 @@ compiler_enter_scope(struct compiler *c, identifier name, } u->u_private = NULL; + u->u_deferred_annotations = NULL; if (scope_type == COMPILER_SCOPE_CLASS) { u->u_static_attributes = PySet_New(0); if (!u->u_static_attributes) { @@ -1587,79 +1590,6 @@ compiler_leave_annotations_scope(struct compiler *c, location loc, return 0; } -static int -compiler_collect_annotations(struct compiler *c, asdl_stmt_seq *stmts, - int *annotations_len) -{ - for (int i = 0; i < asdl_seq_LEN(stmts); i++) { - stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, i); - switch (st->kind) { - case AnnAssign_kind: - // Only "simple" (i.e., unparenthesized) names are stored. - if (st->v.AnnAssign.simple) { - PyObject *mangled = _Py_Mangle(c->u->u_private, st->v.AnnAssign.target->v.Name.id); - ADDOP_LOAD_CONST_NEW(c, LOC(st), mangled); - VISIT(c, expr, st->v.AnnAssign.annotation); - *annotations_len += 1; - } - break; - case For_kind: - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.For.body, annotations_len)); - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.For.orelse, annotations_len)); - break; - case AsyncFor_kind: - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.AsyncFor.body, annotations_len)); - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.AsyncFor.orelse, annotations_len)); - break; - case While_kind: - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.While.body, annotations_len)); - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.While.orelse, annotations_len)); - break; - case If_kind: - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.If.body, annotations_len)); - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.If.orelse, annotations_len)); - break; - case With_kind: - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.With.body, annotations_len)); - break; - case AsyncWith_kind: - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.AsyncWith.body, annotations_len)); - break; - case Match_kind: - for (int j = 0; j < asdl_seq_LEN(st->v.Match.cases); j++) { - match_case_ty match_case = (match_case_ty)asdl_seq_GET( - st->v.Match.cases, j); - RETURN_IF_ERROR(compiler_collect_annotations(c, match_case->body, annotations_len)); - } - break; - case Try_kind: - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.Try.body, annotations_len)); - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.Try.orelse, annotations_len)); - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.Try.finalbody, annotations_len)); - for (int j = 0; j < asdl_seq_LEN(st->v.Try.handlers); j++) { - excepthandler_ty handler = (excepthandler_ty)asdl_seq_GET( - st->v.Try.handlers, j); - RETURN_IF_ERROR(compiler_collect_annotations(c, handler->v.ExceptHandler.body, annotations_len)); - } - break; - case TryStar_kind: - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.TryStar.body, annotations_len)); - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.TryStar.orelse, annotations_len)); - RETURN_IF_ERROR(compiler_collect_annotations(c, st->v.TryStar.finalbody, annotations_len)); - for (int j = 0; j < asdl_seq_LEN(st->v.TryStar.handlers); j++) { - excepthandler_ty handler = (excepthandler_ty)asdl_seq_GET( - st->v.Try.handlers, j); - RETURN_IF_ERROR(compiler_collect_annotations(c, handler->v.ExceptHandler.body, annotations_len)); - } - break; - default: - break; - } - } - return SUCCESS; - -} - /* Compile a sequence of statements, checking for a docstring and for annotations. */ @@ -1711,12 +1641,25 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) if (!(c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && c->u->u_ste->ste_annotation_block != NULL) { void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); + assert(c->u->u_deferred_annotations != NULL); + PyObject *deferred_anno = Py_NewRef(c->u->u_deferred_annotations); RETURN_IF_ERROR(compiler_setup_annotations_scope(c, loc, key, c->u->u_ste->ste_annotation_block->ste_name)); - int annotations_len = 0; - RETURN_IF_ERROR( - compiler_collect_annotations(c, stmts, &annotations_len) - ); + Py_ssize_t annotations_len = PyList_Size(deferred_anno); + for (Py_ssize_t i = 0; i < annotations_len; i++) { + PyObject *ptr = PyList_GET_ITEM(deferred_anno, i); + stmt_ty st = (stmt_ty)PyLong_AsVoidPtr(ptr); + if (st == NULL) { + compiler_exit_scope(c); + Py_DECREF(deferred_anno); + return ERROR; + } + PyObject *mangled = _Py_Mangle(c->u->u_private, st->v.AnnAssign.target->v.Name.id); + ADDOP_LOAD_CONST_NEW(c, LOC(st), mangled); + VISIT(c, expr, st->v.AnnAssign.annotation); + } + Py_DECREF(deferred_anno); + RETURN_IF_ERROR( compiler_leave_annotations_scope(c, loc, annotations_len) ); @@ -6600,20 +6543,37 @@ compiler_annassign(struct compiler *c, stmt_ty s) return ERROR; } /* If we have a simple name in a module or class, store annotation. */ - if ((future_annotations || is_interactive) && - s->v.AnnAssign.simple && + if (s->v.AnnAssign.simple && (c->u->u_scope_type == COMPILER_SCOPE_MODULE || c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { - if (future_annotations) { - VISIT(c, annexpr, s->v.AnnAssign.annotation); + if (future_annotations || is_interactive) { + if (future_annotations) { + VISIT(c, annexpr, s->v.AnnAssign.annotation); + } + else { + VISIT(c, expr, s->v.AnnAssign.annotation); + } + ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); + mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, targ->v.Name.id); + ADDOP_LOAD_CONST_NEW(c, loc, mangled); + ADDOP(c, loc, STORE_SUBSCR); } else { - VISIT(c, expr, s->v.AnnAssign.annotation); + if (c->u->u_deferred_annotations == NULL) { + c->u->u_deferred_annotations = PyList_New(0); + if (c->u->u_deferred_annotations == NULL) { + return ERROR; + } + } + PyObject *ptr = PyLong_FromVoidPtr((void *)s); + if (ptr == NULL) { + return ERROR; + } + if (PyList_Append(c->u->u_deferred_annotations, ptr) < 0) { + Py_DECREF(ptr); + return ERROR; + } } - ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); - mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, targ->v.Name.id); - ADDOP_LOAD_CONST_NEW(c, loc, mangled); - ADDOP(c, loc, STORE_SUBSCR); } break; case Attribute_kind: From 2ab5d0777c139b1f61e983c472cdad23282a1302 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 May 2024 18:08:16 -0700 Subject: [PATCH 47/61] Fix refleak --- Python/compile.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 45ca6b8995c52c..0597b722df4f96 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1640,11 +1640,15 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) // __annotate__ function. See PEP 649. if (!(c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && c->u->u_ste->ste_annotation_block != NULL) { - void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); + assert(c->u->u_deferred_annotations != NULL); PyObject *deferred_anno = Py_NewRef(c->u->u_deferred_annotations); - RETURN_IF_ERROR(compiler_setup_annotations_scope(c, loc, key, - c->u->u_ste->ste_annotation_block->ste_name)); + void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); + if (compiler_setup_annotations_scope(c, loc, key, + c->u->u_ste->ste_annotation_block->ste_name) == -1) { + Py_DECREF(deferred_anno); + return ERROR; + } Py_ssize_t annotations_len = PyList_Size(deferred_anno); for (Py_ssize_t i = 0; i < annotations_len; i++) { PyObject *ptr = PyList_GET_ITEM(deferred_anno, i); @@ -6573,6 +6577,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) Py_DECREF(ptr); return ERROR; } + Py_DECREF(ptr); } } break; From 3b4a645ee8ecb1375c01c5f3ddef286e3f60e64b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 May 2024 18:37:05 -0700 Subject: [PATCH 48/61] Fix a test --- Lib/test/test_positional_only_arg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_positional_only_arg.py b/Lib/test/test_positional_only_arg.py index ad452c032d1614..eea0625012da6d 100644 --- a/Lib/test/test_positional_only_arg.py +++ b/Lib/test/test_positional_only_arg.py @@ -442,7 +442,7 @@ def f(x: not (int is int), /): ... # COMPARE_OP(is), IS_OP (0) # with constant folding we should expect a IS_OP (1) code_obj = next(const for const in g.__code__.co_consts - if isinstance(const, types.CodeType) and "annotations" in const.co_name) + if isinstance(const, types.CodeType) and const.co_name == "__annotate__") codes = [(i.opname, i.argval) for i in dis.get_instructions(code_obj)] self.assertNotIn(('UNARY_NOT', None), codes) self.assertIn(('IS_OP', 1), codes) From 1dfd02bb72a62e7a5f84eec5f7ff40ce3e1c73f9 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 May 2024 19:04:53 -0700 Subject: [PATCH 49/61] Fix bug when there are only non-simple annotations --- Lib/test/test_type_annotations.py | 22 ++++++++++++++++++++++ Python/compile.c | 8 ++++++-- Python/symtable.c | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 205802a1f7b6e2..9e79e8453e3c15 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -339,6 +339,28 @@ def test_no_exotic_expressions(self): check_syntax_error(self, "def func(x: (y := 3)): ...", "named expression cannot be used within an annotation") check_syntax_error(self, "def func(x: (await 42)): ...", "await expression cannot be used within an annotation") + def test_no_exotic_expressions_in_unevaluated_annotations(self): + preludes = [ + "", + "class X: ", + "def f(): ", + "async def f(): ", + ] + for prelude in preludes: + with self.subTest(prelude=prelude): + check_syntax_error(self, prelude + "(x): (yield)", "yield expression cannot be used within an annotation") + check_syntax_error(self, prelude + "(x): (yield from x)", "yield expression cannot be used within an annotation") + check_syntax_error(self, prelude + "(x): (y := 3)", "named expression cannot be used within an annotation") + check_syntax_error(self, prelude + "(x): (await 42)", "await expression cannot be used within an annotation") + + def test_ignore_non_simple_annotations(self): + ns = run_code("class X: (y): int") + self.assertEqual(ns["X"].__annotations__, {}) + ns = run_code("class X: int.b: int") + self.assertEqual(ns["X"].__annotations__, {}) + ns = run_code("class X: int[str]: int") + self.assertEqual(ns["X"].__annotations__, {}) + def test_generated_annotate(self): def func(x: int): pass diff --git a/Python/compile.c b/Python/compile.c index 0597b722df4f96..b150fd84e2a8e2 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1639,9 +1639,13 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) // collect the annotations in a separate pass and generate an // __annotate__ function. See PEP 649. if (!(c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && - c->u->u_ste->ste_annotation_block != NULL) { + c->u->u_deferred_annotations != NULL) { - assert(c->u->u_deferred_annotations != NULL); + // It's possible that ste_annotations_block is set but + // u_deferred_annotations is not, because the former is still + // set if there are only non-simple annotations. However, the + // reverse should not be possible. + assert(c->u->u_ste->ste_annotation_block != NULL); PyObject *deferred_anno = Py_NewRef(c->u->u_deferred_annotations); void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); if (compiler_setup_annotations_scope(c, loc, key, diff --git a/Python/symtable.c b/Python/symtable.c index 7314eb91fb44ee..f04f37d13e50c4 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2500,6 +2500,7 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, VISIT_QUIT(st, 0); } } + // At the top level, we evaluate annotations eagerly, as specified by PEP 649. else if (!is_top_level_interactive) { if (st->st_cur->ste_annotation_block == NULL) { _Py_block_ty current_type = st->st_cur->ste_type; From daba318a722cd3a6c88ca4aa14d52fbc45b90863 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 May 2024 21:31:01 -0700 Subject: [PATCH 50/61] Fix more tests --- Lib/test/test_dis.py | 2 +- Lib/test/test_pyclbr.py | 2 ++ Lib/test/test_pydoc/test_pydoc.py | 4 ++-- Lib/test/test_traceback.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index b0873a4f9381b7..3313a4c5370723 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -363,7 +363,7 @@ def wrap_func_w_kwargs(): CALL 1 STORE_SUBSCR - 2 LOAD_CONST 2 ( at 0x..., file "", line 2>) + 2 LOAD_CONST 2 (", line 2>) MAKE_FUNCTION STORE_NAME 3 (__annotate__) RETURN_CONST 3 (None) diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py index 46206accbafc36..0c12a3085b12af 100644 --- a/Lib/test/test_pyclbr.py +++ b/Lib/test/test_pyclbr.py @@ -109,6 +109,8 @@ def ismethod(oclass, obj, name): actualMethods = [] for m in py_item.__dict__.keys(): + if m == "__annotate__": + continue if ismethod(py_item, getattr(py_item, m), m): actualMethods.append(m) foundMethods = [] diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index b4489d77d19948..afed23b3af5670 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -78,7 +78,7 @@ class A(builtins.object) class B(builtins.object) | Methods defined here: | - | __annotate__ = (...) + | __annotate__(...) | | ---------------------------------------------------------------------- | Data descriptors defined here: @@ -179,7 +179,7 @@ class A(builtins.object) class B(builtins.object) Methods defined here: - __annotate__ = (...) + __annotate__(...) ---------------------------------------------------------------------- Data descriptors defined here: __dict__ diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 3da1671b477cee..1895c88d23b70d 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -632,7 +632,7 @@ def foo(a: THIS_DOES_NOT_EXIST ) -> int: ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+3}, in f_with_type\n' ' foo.__annotations__\n' - f' File "{__file__}", line {lineno_f+1}, in \n' + f' File "{__file__}", line {lineno_f+1}, in __annotate__\n' ' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n' ' ^^^^^^^^^^^^^^^^^^^\n' ) From b066b3d92bf9dfe0f2230cf406ee3f467e8858cd Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 29 May 2024 06:59:53 -0700 Subject: [PATCH 51/61] Remove REPL special case --- Include/internal/pycore_symtable.h | 1 - Python/compile.c | 14 +++++--------- Python/symtable.c | 9 ++------- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index bd1330c3968251..5d544765237df5 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -72,7 +72,6 @@ struct symtable { the symbol table */ int recursion_depth; /* current recursion depth */ int recursion_limit; /* recursion limit */ - int st_kind; /* kind of module */ }; typedef struct _symtable_entry { diff --git a/Python/compile.c b/Python/compile.c index f8caefb29093ca..e2aecbe57ac7eb 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1660,11 +1660,10 @@ compiler_codegen(struct compiler *c, mod_ty mod) } break; case Interactive_kind: - if (c->u->u_ste->ste_annotations_used) { - ADDOP(c, loc, SETUP_ANNOTATIONS); - } c->c_interactive = 1; - VISIT_SEQ(c, stmt, mod->v.Interactive.body); + if (compiler_body(c, loc, mod->v.Interactive.body) < 0) { + return ERROR; + } break; case Expression_kind: VISIT(c, expr, mod->v.Expression.body); @@ -6503,9 +6502,6 @@ compiler_annassign(struct compiler *c, stmt_ty s) { location loc = LOC(s); expr_ty targ = s->v.AnnAssign.target; - bool is_interactive = ( - c->c_st->st_kind == Interactive_kind && c->u->u_scope_type == COMPILER_SCOPE_MODULE - ); bool future_annotations = c->c_future.ff_features & CO_FUTURE_ANNOTATIONS; PyObject *mangled; @@ -6525,7 +6521,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) if (s->v.AnnAssign.simple && (c->u->u_scope_type == COMPILER_SCOPE_MODULE || c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { - if (future_annotations || is_interactive) { + if (future_annotations) { if (future_annotations) { VISIT(c, annexpr, s->v.AnnAssign.annotation); } @@ -6579,7 +6575,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) return ERROR; } /* Annotation is evaluated last. */ - if ((future_annotations || is_interactive) && !s->v.AnnAssign.simple && check_annotation(c, s) < 0) { + if (future_annotations && !s->v.AnnAssign.simple && check_annotation(c, s) < 0) { return ERROR; } return SUCCESS; diff --git a/Python/symtable.c b/Python/symtable.c index f04f37d13e50c4..42025c65cce992 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -430,7 +430,6 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, _PyFutureFeatures *future) } st->st_top = st->st_cur; - st->st_kind = mod->kind; switch (mod->kind) { case Module_kind: seq = mod->v.Module.body; @@ -2491,17 +2490,13 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, struct _symtable_entry *parent_ste, void *key) { int future_annotations = st->st_future->ff_features & CO_FUTURE_ANNOTATIONS; - int is_top_level_interactive = ( - st->st_kind == Interactive_kind && st->st_cur->ste_type == ModuleBlock - ); if (future_annotations) { if(!symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, key, LOCATION(annotation))) { VISIT_QUIT(st, 0); } } - // At the top level, we evaluate annotations eagerly, as specified by PEP 649. - else if (!is_top_level_interactive) { + else { if (st->st_cur->ste_annotation_block == NULL) { _Py_block_ty current_type = st->st_cur->ste_type; if (!symtable_enter_block(st, &_Py_ID(__annotate__), AnnotationBlock, @@ -2536,7 +2531,7 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, } } VISIT(st, expr, annotation); - if ((future_annotations || !is_top_level_interactive) && !symtable_exit_block(st)) { + if (!symtable_exit_block(st)) { VISIT_QUIT(st, 0); } return 1; From 7669361be51e3d5d9d216ac218a4ddf3728ecac8 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 29 May 2024 08:10:10 -0700 Subject: [PATCH 52/61] no docstrings in the repl --- Python/compile.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index e2aecbe57ac7eb..9d6f181de56c1c 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1586,21 +1586,23 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) return SUCCESS; } Py_ssize_t first_instr = 0; - PyObject *docstring = _PyAST_GetDocString(stmts); - if (docstring) { - first_instr = 1; - /* if not -OO mode, set docstring */ - if (c->c_optimize < 2) { - PyObject *cleandoc = _PyCompile_CleanDoc(docstring); - if (cleandoc == NULL) { - return ERROR; + if (!c->c_interactive) { + PyObject *docstring = _PyAST_GetDocString(stmts); + if (docstring) { + first_instr = 1; + /* if not -OO mode, set docstring */ + if (c->c_optimize < 2) { + PyObject *cleandoc = _PyCompile_CleanDoc(docstring); + if (cleandoc == NULL) { + return ERROR; + } + stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, 0); + assert(st->kind == Expr_kind); + location loc = LOC(st->v.Expr.value); + ADDOP_LOAD_CONST(c, loc, cleandoc); + Py_DECREF(cleandoc); + RETURN_IF_ERROR(compiler_nameop(c, NO_LOCATION, &_Py_ID(__doc__), Store)); } - stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, 0); - assert(st->kind == Expr_kind); - location loc = LOC(st->v.Expr.value); - ADDOP_LOAD_CONST(c, loc, cleandoc); - Py_DECREF(cleandoc); - RETURN_IF_ERROR(compiler_nameop(c, NO_LOCATION, &_Py_ID(__doc__), Store)); } } for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(stmts); i++) { From c6a1b80f5a321f3eab98efb07af495d054580465 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 29 May 2024 08:47:52 -0700 Subject: [PATCH 53/61] Fix pyrepl test --- Lib/test/test_pyrepl/test_interact.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pyrepl/test_interact.py b/Lib/test/test_pyrepl/test_interact.py index 6bc297d8d2aa6b..72ed3a00190f0f 100644 --- a/Lib/test/test_pyrepl/test_interact.py +++ b/Lib/test/test_pyrepl/test_interact.py @@ -105,7 +105,7 @@ def test_runsource_shows_syntax_error_for_failed_compilation(self): def test_no_active_future(self): console = InteractiveColoredConsole() - source = "if True:\n x: int = 1; print(__annotations__)" + source = "x: int = 1; print(__annotate__(1))" f = io.StringIO() with contextlib.redirect_stdout(f): result = console.runsource(source) From 5cdbdd732dc8ae63ea34a176cefdc8c6d6b40d12 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 29 May 2024 22:26:49 -0700 Subject: [PATCH 54/61] CR feedback --- Lib/test/test_type_annotations.py | 2 ++ Lib/typing.py | 6 +++++- Python/compile.c | 17 ++++++----------- Python/symtable.c | 21 ++++++++++----------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 9e79e8453e3c15..25dc4d883eb356 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -374,6 +374,8 @@ class X: self.assertEqual(annotate.__name__, "__annotate__") with self.assertRaises(NotImplementedError): annotate(inspect.FORWARDREF) + with self.assertRaises(NotImplementedError): + annotate(inspect.SOURCE) with self.assertRaises(NotImplementedError): annotate(None) self.assertEqual(annotate(inspect.VALUE), {"x": int}) diff --git a/Lib/typing.py b/Lib/typing.py index 15307f28de6c8a..f158530359ab1b 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3154,7 +3154,11 @@ def __new__(cls, name, bases, ns, total=True): mutable_keys = set() for base in bases: - # TODO: preserve laziness + # TODO: Avoid eagerly evaluating annotations in VALUE format. + # Instead, evaluate in FORWARDREF format to figure out which + # keys have Required/NotRequired/ReadOnly qualifiers, and create + # a new __annotate__ function for the resulting TypedDict that + # combines the annotations from this class and its parents. annotations.update(base.__annotations__) base_required = base.__dict__.get('__required_keys__', set()) diff --git a/Python/compile.c b/Python/compile.c index 9d6f181de56c1c..96a4a38bf38e85 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1616,7 +1616,8 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) // It's possible that ste_annotations_block is set but // u_deferred_annotations is not, because the former is still - // set if there are only non-simple annotations. However, the + // set if there are only non-simple annotations (i.e., annotations + // for attributes, subscripts, or parenthesized names). However, the // reverse should not be possible. assert(c->u->u_ste->ste_annotation_block != NULL); PyObject *deferred_anno = Py_NewRef(c->u->u_deferred_annotations); @@ -2368,7 +2369,6 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async) asdl_expr_seq *decos; asdl_type_param_seq *type_params; Py_ssize_t funcflags; - int annotations; int firstlineno; if (is_async) { @@ -2434,14 +2434,14 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async) } } - annotations = compiler_visit_annotations(c, loc, args, returns); - if (annotations < 0) { + int annotations_flag = compiler_visit_annotations(c, loc, args, returns); + if (annotations_flag < 0) { if (is_generic) { compiler_exit_scope(c); } return ERROR; } - funcflags |= annotations; + funcflags |= annotations_flag; if (compiler_function_body(c, s, is_async, funcflags, firstlineno) < 0) { if (is_generic) { @@ -6524,12 +6524,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) (c->u->u_scope_type == COMPILER_SCOPE_MODULE || c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { if (future_annotations) { - if (future_annotations) { - VISIT(c, annexpr, s->v.AnnAssign.annotation); - } - else { - VISIT(c, expr, s->v.AnnAssign.annotation); - } + VISIT(c, annexpr, s->v.AnnAssign.annotation); ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, targ->v.Name.id); ADDOP_LOAD_CONST_NEW(c, loc, mangled); diff --git a/Python/symtable.c b/Python/symtable.c index 42025c65cce992..ebf936ab25799a 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -248,8 +248,7 @@ static int symtable_visit_alias(struct symtable *st, alias_ty); static int symtable_visit_comprehension(struct symtable *st, comprehension_ty); static int symtable_visit_keyword(struct symtable *st, keyword_ty); static int symtable_visit_params(struct symtable *st, asdl_arg_seq *args); -static int symtable_visit_annotation(struct symtable *st, expr_ty annotation, - struct _symtable_entry *parent_ste, void *key); +static int symtable_visit_annotation(struct symtable *st, expr_ty annotation, void *key); static int symtable_visit_argannotations(struct symtable *st, asdl_arg_seq *args); static int symtable_implicit_arg(struct symtable *st, int pos); static int symtable_visit_annotations(struct symtable *st, stmt_ty, arguments_ty, expr_ty, @@ -1361,9 +1360,9 @@ symtable_enter_existing_block(struct symtable *st, PySTEntryObject* ste) /* The entry is owned by the stack. Borrow it for st_cur. */ st->st_cur = ste; - /* Annotation blocks shouldn't have any affect on the symbol table since in - * the compilation stage, they will all be transformed to strings. They are - * only created if future 'annotations' feature is activated. */ + /* If "from __future__ import annotations" is active, + * annotation blocks shouldn't have any affect on the symbol table since in + * the compilation stage, they will all be transformed to strings. */ if (st->st_future->ff_features & CO_FUTURE_ANNOTATIONS && ste->ste_type == AnnotationBlock) { return 1; } @@ -1843,7 +1842,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) else { VISIT(st, expr, s->v.AnnAssign.target); } - if (!symtable_visit_annotation(st, s->v.AnnAssign.annotation, st->st_cur, + if (!symtable_visit_annotation(st, s->v.AnnAssign.annotation, (void *)((uintptr_t)st->st_cur->ste_id + 1))) { VISIT_QUIT(st, 0); } @@ -2486,9 +2485,9 @@ symtable_visit_params(struct symtable *st, asdl_arg_seq *args) } static int -symtable_visit_annotation(struct symtable *st, expr_ty annotation, - struct _symtable_entry *parent_ste, void *key) +symtable_visit_annotation(struct symtable *st, expr_ty annotation, void *key) { + struct _symtable_entry *parent_ste = st->st_cur; int future_annotations = st->st_future->ff_features & CO_FUTURE_ANNOTATIONS; if (future_annotations) { if(!symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, @@ -2497,8 +2496,8 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, } } else { - if (st->st_cur->ste_annotation_block == NULL) { - _Py_block_ty current_type = st->st_cur->ste_type; + if (parent_ste->ste_annotation_block == NULL) { + _Py_block_ty current_type = parent_ste->ste_type; if (!symtable_enter_block(st, &_Py_ID(__annotate__), AnnotationBlock, key, LOCATION(annotation))) { VISIT_QUIT(st, 0); @@ -2525,7 +2524,7 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, } } else { - if (!symtable_enter_existing_block(st, st->st_cur->ste_annotation_block)) { + if (!symtable_enter_existing_block(st, parent_ste->ste_annotation_block)) { VISIT_QUIT(st, 0); } } From bd469abc2bb1d7c438ef622c4c429ffc0a5b1ccb Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 31 May 2024 18:19:12 -0700 Subject: [PATCH 55/61] Still generate __annotate__ if "from __future__ import annotations" is on This makes it so functools.update_wrapper can always copy __annotate__ without having to worry about whether or not the future is enabled. --- Lib/test/test_type_annotations.py | 13 +++++++ Python/compile.c | 23 ++++-------- Python/symtable.c | 60 ++++++++++++++----------------- 3 files changed, 46 insertions(+), 50 deletions(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 25dc4d883eb356..810201746edf11 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -384,3 +384,16 @@ def test_comprehension_in_annotation(self): # This crashed in an earlier version of the code ns = run_code("x: [y for y in range(10)]") self.assertEqual(ns["__annotate__"](1), {"x": list(range(10))}) + + def test_future_annotations(self): + code = """ + from __future__ import annotations + + def f(x: int) -> int: pass + """ + ns = run_code(textwrap.dedent(code)) + f = ns["f"] + self.assertIsInstance(f.__annotate__, types.FunctionType) + annos = {"x": "int", "return": "int"} + self.assertEqual(f.__annotate__(inspect.VALUE), annos) + self.assertEqual(f.__annotations__, annos) diff --git a/Python/compile.c b/Python/compile.c index fd91c72f1fa4a5..fa81cf463cb902 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1920,7 +1920,6 @@ compiler_visit_annotations(struct compiler *c, location loc, Return -1 on error, or a combination of flags to add to the function. */ Py_ssize_t annotations_len = 0; - int future_annotations = c->c_future.ff_features & CO_FUTURE_ANNOTATIONS; PySTEntryObject *ste; int result = _PySymtable_LookupOptional(c->c_st, args, &ste); @@ -1930,7 +1929,7 @@ compiler_visit_annotations(struct compiler *c, location loc, assert(ste != NULL); bool annotations_used = ste->ste_annotations_used; - if (annotations_used && !future_annotations) { + if (annotations_used) { if (compiler_setup_annotations_scope(c, loc, (void *)args, ste->ste_name) < 0) { Py_DECREF(ste); @@ -1940,25 +1939,17 @@ compiler_visit_annotations(struct compiler *c, location loc, Py_DECREF(ste); if (compiler_visit_annotations_in_scope(c, loc, args, returns, &annotations_len) < 0) { - if (annotations_used && !future_annotations) { + if (annotations_used) { compiler_exit_scope(c); } return ERROR; } - if (future_annotations) { - if (annotations_len) { - ADDOP_I(c, loc, BUILD_TUPLE, annotations_len * 2); - return MAKE_FUNCTION_ANNOTATIONS; - } - } - else { - if (annotations_used) { - RETURN_IF_ERROR( - compiler_leave_annotations_scope(c, loc, annotations_len) - ); - return MAKE_FUNCTION_ANNOTATE; - } + if (annotations_used) { + RETURN_IF_ERROR( + compiler_leave_annotations_scope(c, loc, annotations_len) + ); + return MAKE_FUNCTION_ANNOTATE; } return 0; diff --git a/Python/symtable.c b/Python/symtable.c index ebf936ab25799a..e53cef9bf2d0fb 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2488,45 +2488,37 @@ static int symtable_visit_annotation(struct symtable *st, expr_ty annotation, void *key) { struct _symtable_entry *parent_ste = st->st_cur; - int future_annotations = st->st_future->ff_features & CO_FUTURE_ANNOTATIONS; - if (future_annotations) { - if(!symtable_enter_block(st, &_Py_ID(_annotation), AnnotationBlock, - key, LOCATION(annotation))) { + if (parent_ste->ste_annotation_block == NULL) { + _Py_block_ty current_type = parent_ste->ste_type; + if (!symtable_enter_block(st, &_Py_ID(__annotate__), AnnotationBlock, + key, LOCATION(annotation))) { VISIT_QUIT(st, 0); } - } - else { - if (parent_ste->ste_annotation_block == NULL) { - _Py_block_ty current_type = parent_ste->ste_type; - if (!symtable_enter_block(st, &_Py_ID(__annotate__), AnnotationBlock, - key, LOCATION(annotation))) { - VISIT_QUIT(st, 0); - } - parent_ste->ste_annotation_block = - (struct _symtable_entry *)Py_NewRef(st->st_cur); - if (current_type == ClassBlock) { - st->st_cur->ste_can_see_class_scope = 1; - if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(annotation))) { - return 0; - } - } - - _Py_DECLARE_STR(format, ".format"); - // The generated __annotate__ function takes a single parameter with the - // internal name ".format". - if (!symtable_add_def(st, &_Py_STR(format), DEF_PARAM, - LOCATION(annotation))) { - return 0; - } - if (!symtable_add_def(st, &_Py_STR(format), USE, - LOCATION(annotation))) { + parent_ste->ste_annotation_block = + (struct _symtable_entry *)Py_NewRef(st->st_cur); + int future_annotations = st->st_future->ff_features & CO_FUTURE_ANNOTATIONS; + if (current_type == ClassBlock && !future_annotations) { + st->st_cur->ste_can_see_class_scope = 1; + if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(annotation))) { return 0; } } - else { - if (!symtable_enter_existing_block(st, parent_ste->ste_annotation_block)) { - VISIT_QUIT(st, 0); - } + + _Py_DECLARE_STR(format, ".format"); + // The generated __annotate__ function takes a single parameter with the + // internal name ".format". + if (!symtable_add_def(st, &_Py_STR(format), DEF_PARAM, + LOCATION(annotation))) { + return 0; + } + if (!symtable_add_def(st, &_Py_STR(format), USE, + LOCATION(annotation))) { + return 0; + } + } + else { + if (!symtable_enter_existing_block(st, parent_ste->ste_annotation_block)) { + VISIT_QUIT(st, 0); } } VISIT(st, expr, annotation); From 278de22c69d81520ec12976ebdd3ae2851b11f78 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 31 May 2024 22:09:42 -0700 Subject: [PATCH 56/61] Regen globals --- Include/internal/pycore_global_objects_fini_generated.h | 1 - Include/internal/pycore_global_strings.h | 1 - Include/internal/pycore_runtime_init_generated.h | 1 - Include/internal/pycore_unicodeobject_generated.h | 3 --- 4 files changed, 6 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index b9480728869b36..474d8205e95ec4 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -747,7 +747,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_abstract_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_active)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_align_)); - _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_annotation)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_anonymous_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_argtypes_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_as_parameter_)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index d0821a6b19cc03..373e2ce8037ed7 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -236,7 +236,6 @@ struct _Py_global_strings { STRUCT_FOR_ID(_abstract_) STRUCT_FOR_ID(_active) STRUCT_FOR_ID(_align_) - STRUCT_FOR_ID(_annotation) STRUCT_FOR_ID(_anonymous_) STRUCT_FOR_ID(_argtypes_) STRUCT_FOR_ID(_as_parameter_) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index a0ee110bbfcba4..598610b6ab4def 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -745,7 +745,6 @@ extern "C" { INIT_ID(_abstract_), \ INIT_ID(_active), \ INIT_ID(_align_), \ - INIT_ID(_annotation), \ INIT_ID(_anonymous_), \ INIT_ID(_argtypes_), \ INIT_ID(_as_parameter_), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 892f580e8a6846..568474f06e0747 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -546,9 +546,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(_align_); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); - string = &_Py_ID(_annotation); - assert(_PyUnicode_CheckConsistency(string, 1)); - _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(_anonymous_); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); From 6b563db05e64ca92deed75c497d5e5b0f042fd4e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 8 Jun 2024 06:21:34 -0600 Subject: [PATCH 57/61] =?UTF-8?q?Feedback=20from=20B=C3=A9n=C3=A9dikt=20Tr?= =?UTF-8?q?an?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/test/test_grammar.py | 4 ++-- .../2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst | 2 +- Python/compile.c | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index aa10efb892b953..4b25f07fe55061 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -439,9 +439,9 @@ class F(C, A): def test_var_annot_module_semantics(self): self.assertEqual(test.__annotations__, {}) self.assertEqual(ann_module.__annotations__, - {'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float}) + {'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float}) self.assertEqual(ann_module.M.__annotations__, - {'o': type}) + {'o': type}) self.assertEqual(ann_module2.__annotations__, {}) def test_var_annot_in_module(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst index dfb3958535a16b..265ffb32e6a1f9 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-22-06-22-47.gh-issue-119180.vZMiXm.rst @@ -1 +1 @@ -Evaluation of annotations is now deferred. See PEP 649 for details. +Evaluation of annotations is now deferred. See :pep:`649` for details. diff --git a/Python/compile.c b/Python/compile.c index a1833e8ca9dc18..c3372766d0bd50 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1922,8 +1922,7 @@ compiler_visit_annotations(struct compiler *c, location loc, Py_ssize_t annotations_len = 0; PySTEntryObject *ste; - int result = _PySymtable_LookupOptional(c->c_st, args, &ste); - if (result == -1) { + if (_PySymtable_LookupOptional(c->c_st, args, &ste) < 0) { return ERROR; } assert(ste != NULL); From 0058b822b9d7a87eaab0c9b4cea2987c6ea2ac06 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 8 Jun 2024 21:36:19 -0600 Subject: [PATCH 58/61] add test --- Lib/test/test_type_annotations.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 810201746edf11..9e0b30d77a7fee 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -391,9 +391,20 @@ def test_future_annotations(self): def f(x: int) -> int: pass """ - ns = run_code(textwrap.dedent(code)) + ns = run_code(code) f = ns["f"] self.assertIsInstance(f.__annotate__, types.FunctionType) annos = {"x": "int", "return": "int"} self.assertEqual(f.__annotate__(inspect.VALUE), annos) self.assertEqual(f.__annotations__, annos) + + def test_name_clash_with_format(self): + # this test would fail if __annotate__'s parameter was called "format" + code = """ + class format: pass + + def f(x: format): pass + """ + ns = run_code(code) + f = ns["f"] + self.assertEqual(f.__annotations__, {"x": ns["format"]}) From f72dbca0be413ad03900e7a939e9277fccbb159a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 10 Jun 2024 23:07:31 -0600 Subject: [PATCH 59/61] Undo changes to inspect that we do not want --- Lib/inspect.py | 11 ----------- Lib/test/test_type_annotations.py | 12 ++++++++---- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index b99f43781866fc..5570a43ebfea19 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -38,7 +38,6 @@ "AGEN_CREATED", "AGEN_RUNNING", "AGEN_SUSPENDED", - "AnnotationsFormat", "ArgInfo", "Arguments", "Attribute", @@ -62,7 +61,6 @@ "ClassFoundException", "ClosureVars", "EndOfBlock", - "FORWARDREF", "FrameInfo", "FullArgSpec", "GEN_CLOSED", @@ -136,11 +134,9 @@ "istraceback", "markcoroutinefunction", "signature", - "SOURCE", "stack", "trace", "unwrap", - "VALUE", "walktree", ] @@ -177,13 +173,6 @@ TPFLAGS_IS_ABSTRACT = 1 << 20 -@enum.global_enum -class AnnotationsFormat(enum.IntEnum): - VALUE = 1 - FORWARDREF = 2 - SOURCE = 3 - - def get_annotations(obj, *, globals=None, locals=None, eval_str=False): """Compute the annotations dict for an object. diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 9e0b30d77a7fee..908fe7da09a2ac 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -4,6 +4,10 @@ import unittest from test.support import run_code, check_syntax_error +VALUE = 1 +FORWARDREF = 2 +SOURCE = 3 + class TypeAnnotationTests(unittest.TestCase): @@ -373,12 +377,12 @@ class X: self.assertIsInstance(annotate, types.FunctionType) self.assertEqual(annotate.__name__, "__annotate__") with self.assertRaises(NotImplementedError): - annotate(inspect.FORWARDREF) + annotate(FORWARDREF) with self.assertRaises(NotImplementedError): - annotate(inspect.SOURCE) + annotate(SOURCE) with self.assertRaises(NotImplementedError): annotate(None) - self.assertEqual(annotate(inspect.VALUE), {"x": int}) + self.assertEqual(annotate(VALUE), {"x": int}) def test_comprehension_in_annotation(self): # This crashed in an earlier version of the code @@ -395,7 +399,7 @@ def f(x: int) -> int: pass f = ns["f"] self.assertIsInstance(f.__annotate__, types.FunctionType) annos = {"x": "int", "return": "int"} - self.assertEqual(f.__annotate__(inspect.VALUE), annos) + self.assertEqual(f.__annotate__(VALUE), annos) self.assertEqual(f.__annotations__, annos) def test_name_clash_with_format(self): From 8674eabf95ce294517d1e00aee06e9cb7b649cad Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 10 Jun 2024 23:17:32 -0600 Subject: [PATCH 60/61] fix --- Lib/test/test_type_annotations.py | 1 - Lib/typing.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 908fe7da09a2ac..a9be1f5aa84681 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -1,4 +1,3 @@ -import inspect import textwrap import types import unittest diff --git a/Lib/typing.py b/Lib/typing.py index f158530359ab1b..7a9149d3f3c2c1 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -24,7 +24,6 @@ import collections.abc import copyreg import functools -import inspect import operator import sys import types @@ -2974,7 +2973,7 @@ def __new__(cls, typename, bases, ns): if "__annotations__" in ns: types = ns["__annotations__"] elif "__annotate__" in ns: - types = ns["__annotate__"](inspect.VALUE) + types = ns["__annotate__"](1) # VALUE else: types = {} default_names = [] @@ -3140,7 +3139,7 @@ def __new__(cls, name, bases, ns, total=True): if "__annotations__" in ns: own_annotations = ns["__annotations__"] elif "__annotate__" in ns: - own_annotations = ns["__annotate__"](inspect.VALUE) + own_annotations = ns["__annotate__"](1) # VALUE else: own_annotations = {} msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" From ee11fd979fa51152728122f8313a51096a491f47 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 11 Jun 2024 06:40:50 -0600 Subject: [PATCH 61/61] Fix more tests --- Lib/test/test_grammar.py | 2 +- Lib/test/test_inspect/test_inspect.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index 4b25f07fe55061..5b7a639c025a0f 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -459,7 +459,7 @@ def test_var_annot_simple_exec(self): gns = {}; lns = {} exec("'docstring'\n" "x: int = 5\n", gns, lns) - self.assertEqual(lns["__annotate__"](inspect.VALUE), {'x': int}) + self.assertEqual(lns["__annotate__"](1), {'x': int}) with self.assertRaises(KeyError): gns['__annotate__'] diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 9ca2b059f6fb5f..0a4fa9343f15e0 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1593,11 +1593,6 @@ class C(metaclass=M): attrs = [a[0] for a in inspect.getmembers(C)] self.assertNotIn('missing', attrs) - def test_annotation_format(self): - self.assertIs(inspect.VALUE, inspect.AnnotationsFormat.VALUE) - self.assertEqual(inspect.VALUE.value, 1) - self.assertEqual(inspect.VALUE, 1) - def test_get_annotations_with_stock_annotations(self): def foo(a:int, b:str): pass self.assertEqual(inspect.get_annotations(foo), {'a': int, 'b': str})