]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-133778: Fix setting `__annotations__` under PEP 563 (#133794)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Sun, 25 May 2025 15:38:18 +0000 (08:38 -0700)
committerGitHub <noreply@github.com>
Sun, 25 May 2025 15:38:18 +0000 (08:38 -0700)
Lib/test/test_type_annotations.py
Misc/NEWS.d/next/Core_and_Builtins/2025-05-09-18-11-21.gh-issue-133778.pWEV3t.rst [new file with mode: 0644]
Objects/typeobject.c

index 2c886bb6d362fa89f06782ea440baed59060261e..c66cb058552643cc041d61eb41b791bbf78314c5 100644 (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
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-09-18-11-21.gh-issue-133778.pWEV3t.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-09-18-11-21.gh-issue-133778.pWEV3t.rst
new file mode 100644 (file)
index 0000000..6eb6881
--- /dev/null
@@ -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.
index a7ab69fef4c7214174cad9b499b27c5fe4327630..ee09289425b91ad1b6f4d0b7b3992416dc3b0931 100644 (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);