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.
_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__));
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__)
INIT_ID(__all__), \
INIT_ID(__and__), \
INIT_ID(__anext__), \
+ INIT_ID(__annotate__), \
INIT_ID(__annotations__), \
INIT_ID(__args__), \
INIT_ID(__asyncio_running_event_loop__), \
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);
check(x, size('3Pi2cP7P2ic??2P'))
# function
def func(): pass
- check(func, size('15Pi'))
+ check(func, size('16Pi'))
class c():
@staticmethod
def foo():
import textwrap
+import types
import unittest
from test.support import run_code
case 0:
x: int = 1
""")
+
+
+class AnnotateTests(unittest.TestCase):
+ """See PEP 649."""
+ def test_manual_annotate(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.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"):
+ print(f.__annotations__)
+
+ f.__annotate__ = lambda x: 42
+ with self.assertRaisesRegex(TypeError, r"__annotate__ returned non-dict of type 'int'"):
+ 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)
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(
'__init__', '__module__', '__new__', '__slots__',
'__subclasshook__', '__weakref__', '__class_getitem__',
'__match_args__', '__static_attributes__', '__firstlineno__',
+ '__annotate__',
})
# These special attributes will be not collected as protocol members.
--- /dev/null
+Add an ``__annotate__`` attribute to functions, classes, and modules as part
+of :pep:`649`. Patch by Jelle Zijlstra.
#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()
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;
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;
func_get_annotation_dict(PyFunctionObject *op)
{
if (op->func_annotations == NULL) {
- return NULL;
+ if (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate)) {
+ Py_RETURN_NONE;
+ }
+ PyObject *one = _PyLong_GetOne();
+ PyObject *ann_dict = _PyObject_CallOneArg(op->func_annotate, one);
+ if (ann_dict == NULL) {
+ return NULL;
+ }
+ if (!PyDict_Check(ann_dict)) {
+ PyErr_Format(PyExc_TypeError, "__annotate__ returned non-dict of type '%.100s'",
+ Py_TYPE(ann_dict)->tp_name);
+ 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;
"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;
}
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 callable 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;
return -1;
}
Py_XSETREF(op->func_annotations, Py_XNewRef(value));
+ Py_CLEAR(op->func_annotate);
return 0;
}
(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},
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.
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;
#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
};
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) {
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;
+ if (PyDict_SetItem(dict, &_Py_ID(__annotate__), annotate) == -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) {
+ Py_DECREF(annotate);
+ Py_DECREF(dict);
+ return NULL;
+ }
+ if (!PyDict_Check(annotations)) {
+ PyErr_Format(PyExc_TypeError, "__annotate__ returned non-dict of type '%.100s'",
+ Py_TYPE(annotations)->tp_name);
+ Py_DECREF(annotate);
+ Py_DECREF(annotations);
+ Py_DECREF(dict);
+ return NULL;
+ }
+ }
+ else {
+ annotations = PyDict_New();
+ }
+ Py_XDECREF(annotate);
if (annotations) {
int result = PyDict_SetItem(
dict, &_Py_ID(__annotations__), annotations);
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, "<module>.__dict__ is not a dictionary");
- goto exit;
- }
if (value != NULL) {
/* set */
ret = 0;
}
}
+ if (ret == 0 && PyDict_Pop(dict, &_Py_ID(__annotate__), NULL) < 0) {
+ ret = -1;
+ }
-exit:
Py_DECREF(dict);
return ret;
}
static PyGetSetDef module_getsets[] = {
{"__annotations__", (getter)module_get_annotations, (setter)module_set_annotations},
+ {"__annotate__", (getter)module_get_annotate, (setter)module_set_annotate},
{NULL}
};
#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()
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 = PyType_GetDict(type);
+ if (PyDict_GetItemRef(dict, &_Py_ID(__annotate__), &annotate) < 0) {
+ Py_DECREF(dict);
+ 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) {
+ Py_DECREF(dict);
+ return NULL;
+ }
+ }
+ Py_DECREF(dict);
+ 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 = PyType_GetDict(type);
+ assert(PyDict_Check(dict));
+ int result = PyDict_SetItem(dict, &_Py_ID(__annotate__), value);
+ if (result < 0) {
+ Py_DECREF(dict);
+ return -1;
+ }
+ if (!Py_IsNone(value)) {
+ if (PyDict_Pop(dict, &_Py_ID(__annotations__), NULL) == -1) {
+ Py_DECREF(dict);
+ PyType_Modified(type);
+ return -1;
+ }
+ }
+ Py_DECREF(dict);
+ PyType_Modified(type);
+ return 0;
+}
+
static PyObject *
type_get_annotations(PyTypeObject *type, void *context)
{
}
PyObject *annotations;
- PyObject *dict = lookup_tp_dict(type);
+ PyObject *dict = PyType_GetDict(type);
if (PyDict_GetItemRef(dict, &_Py_ID(__annotations__), &annotations) < 0) {
+ Py_DECREF(dict);
return NULL;
}
if (annotations) {
}
}
else {
- annotations = PyDict_New();
+ PyObject *annotate = type_get_annotate(type, NULL);
+ if (annotate == NULL) {
+ Py_DECREF(dict);
+ return NULL;
+ }
+ if (PyCallable_Check(annotate)) {
+ PyObject *one = _PyLong_GetOne();
+ annotations = _PyObject_CallOneArg(annotate, one);
+ if (annotations == NULL) {
+ Py_DECREF(dict);
+ Py_DECREF(annotate);
+ return NULL;
+ }
+ if (!PyDict_Check(annotations)) {
+ PyErr_Format(PyExc_TypeError, "__annotate__ returned non-dict of type '%.100s'",
+ Py_TYPE(annotations)->tp_name);
+ Py_DECREF(annotations);
+ Py_DECREF(annotate);
+ Py_DECREF(dict);
+ return NULL;
+ }
+ }
+ else {
+ annotations = PyDict_New();
+ }
+ Py_DECREF(annotate);
if (annotations) {
int result = PyDict_SetItem(
dict, &_Py_ID(__annotations__), annotations);
}
}
}
+ Py_DECREF(dict);
return annotations;
}
}
int result;
- PyObject *dict = lookup_tp_dict(type);
+ PyObject *dict = PyType_GetDict(type);
if (value != NULL) {
/* set */
result = PyDict_SetItem(dict, &_Py_ID(__annotations__), value);
result = PyDict_Pop(dict, &_Py_ID(__annotations__), NULL);
if (result == 0) {
PyErr_SetString(PyExc_AttributeError, "__annotations__");
+ Py_DECREF(dict);
return -1;
}
}
if (result < 0) {
+ Py_DECREF(dict);
return -1;
}
-
+ else if (result == 0) {
+ if (PyDict_Pop(dict, &_Py_ID(__annotate__), NULL) < 0) {
+ PyType_Modified(type);
+ Py_DECREF(dict);
+ return -1;
+ }
+ }
PyType_Modified(type);
+ Py_DECREF(dict);
return 0;
}
{"__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}
};