gh-133778: Fix setting __annotations__ under PEP 563 (#133794)

This commit is contained in:
Jelle Zijlstra 2025-05-25 08:38:18 -07:00 committed by GitHub
parent 91b48868a8
commit 4443110c34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 60 additions and 9 deletions

View file

@ -498,6 +498,28 @@ class DeferredEvaluationTests(unittest.TestCase):
self.assertEqual(f.__annotate__(annotationlib.Format.VALUE), annos)
self.assertEqual(f.__annotations__, annos)
def test_set_annotations(self):
function_code = textwrap.dedent("""
def f(x: int):
pass
""")
class_code = textwrap.dedent("""
class f:
x: int
""")
for future in (False, True):
for label, code in (("function", function_code), ("class", class_code)):
with self.subTest(future=future, label=label):
if future:
code = "from __future__ import annotations\n" + code
ns = run_code(code)
f = ns["f"]
anno = "int" if future else int
self.assertEqual(f.__annotations__, {"x": anno})
f.__annotations__ = {"x": str}
self.assertEqual(f.__annotations__, {"x": str})
def test_name_clash_with_format(self):
# this test would fail if __annotate__'s parameter was called "format"
# during symbol table construction

View file

@ -0,0 +1,2 @@
Fix bug where assigning to the :attr:`~type.__annotations__` attributes of
classes defined under ``from __future__ import annotations`` had no effect.

View file

@ -2065,19 +2065,46 @@ type_set_annotations(PyObject *tp, PyObject *value, void *Py_UNUSED(closure))
return -1;
}
int result;
PyObject *dict = PyType_GetDict(type);
if (value != NULL) {
/* set */
result = PyDict_SetItem(dict, &_Py_ID(__annotations_cache__), value);
} else {
/* delete */
result = PyDict_Pop(dict, &_Py_ID(__annotations_cache__), NULL);
if (result == 0) {
PyErr_SetString(PyExc_AttributeError, "__annotations__");
int result = PyDict_ContainsString(dict, "__annotations__");
if (result < 0) {
Py_DECREF(dict);
return -1;
}
if (result) {
// If __annotations__ is currently in the dict, we update it,
if (value != NULL) {
result = PyDict_SetItem(dict, &_Py_ID(__annotations__), value);
} else {
result = PyDict_Pop(dict, &_Py_ID(__annotations__), NULL);
if (result == 0) {
// Somebody else just deleted it?
PyErr_SetString(PyExc_AttributeError, "__annotations__");
Py_DECREF(dict);
return -1;
}
}
if (result < 0) {
Py_DECREF(dict);
return -1;
}
// Also clear __annotations_cache__ just in case.
result = PyDict_Pop(dict, &_Py_ID(__annotations_cache__), NULL);
}
else {
// Else we update only __annotations_cache__.
if (value != NULL) {
/* set */
result = PyDict_SetItem(dict, &_Py_ID(__annotations_cache__), value);
} else {
/* delete */
result = PyDict_Pop(dict, &_Py_ID(__annotations_cache__), NULL);
if (result == 0) {
PyErr_SetString(PyExc_AttributeError, "__annotations__");
Py_DECREF(dict);
return -1;
}
}
}
if (result < 0) {
Py_DECREF(dict);