Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Lib/test/test_type_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,18 @@ def check_annotations(self, f):
f.__annotations__ = {"z": 43}
self.assertIs(f.__annotate__, None)

def test_annotate_sets_annotations(self):
# If __annotate__ sets the function's annotations while it runs,
# those are kept instead of the result of the call.
def f():
pass
def annotate(format):
f.__annotations__ = {"x": int}
return {"y": str}
f.__annotate__ = annotate
self.assertEqual(f.__annotations__, {"x": int})
self.assertIs(f.__annotate__, None)

def test_user_defined_annotate(self):
class X:
a: int
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix reading a function's :attr:`~function.__annotations__` using borrowed
references to :attr:`~function.__annotate__` and the internal annotations
tuple while running code that can replace them. If ``__annotations__`` is
set while ``__annotate__`` runs, the newly set value is now kept.
28 changes: 23 additions & 5 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -539,11 +539,18 @@ static PyObject *
func_get_annotation_dict(PyFunctionObject *op)
{
if (op->func_annotations == NULL) {
if (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate)) {
PyObject *annotate = op->func_annotate;
if (annotate == NULL || !PyCallable_Check(annotate)) {
Py_RETURN_NONE;
}
// Calling __annotate__ can run arbitrary code which may replace
// func_annotate or func_annotations, either directly or from another
// thread while the critical section is suspended. Keep the callable
// alive during the call.
Py_INCREF(annotate);
PyObject *one = _PyLong_GetOne();
PyObject *ann_dict = _PyObject_CallOneArg(op->func_annotate, one);
PyObject *ann_dict = _PyObject_CallOneArg(annotate, one);
Py_DECREF(annotate);
if (ann_dict == NULL) {
return NULL;
}
Expand All @@ -554,13 +561,22 @@ func_get_annotation_dict(PyFunctionObject *op)
Py_DECREF(ann_dict);
return NULL;
}
Py_XSETREF(op->func_annotations, ann_dict);
if (op->func_annotations != NULL) {
// Annotations were set during the call, keep them.
Py_DECREF(ann_dict);
assert(PyDict_Check(op->func_annotations));
return op->func_annotations;
}
op->func_annotations = ann_dict;
return ann_dict;
}
if (PyTuple_CheckExact(op->func_annotations)) {
PyObject *ann_tuple = op->func_annotations;
// Keep the tuple alive while building the dict, since it can be
// replaced if the critical section is suspended.
PyObject *ann_tuple = Py_NewRef(op->func_annotations);
PyObject *ann_dict = PyDict_New();
if (ann_dict == NULL) {
Py_DECREF(ann_tuple);
return NULL;
}

Expand All @@ -573,10 +589,12 @@ func_get_annotation_dict(PyFunctionObject *op)

if (err < 0) {
Py_DECREF(ann_dict);
Py_DECREF(ann_tuple);
return NULL;
}
}
Py_SETREF(op->func_annotations, ann_dict);
Py_XSETREF(op->func_annotations, ann_dict);
Py_DECREF(ann_tuple);
}
assert(PyDict_Check(op->func_annotations));
return op->func_annotations;
Expand Down
Loading