]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-137814: Fix __qualname__ of __annotate__ functions in the interpreter ...
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Thu, 16 Apr 2026 04:52:43 +0000 (21:52 -0700)
committerGitHub <noreply@github.com>
Thu, 16 Apr 2026 04:52:43 +0000 (21:52 -0700)
gh-137814: [3.14] Fix __qualname__ of __annotate__ functions in the interpreter

I'd still like to do #137842 on 3.15+, but that requires changing bytecode and we can't
really afford to do that in 3.14. So to fix this in 3.14, let's patch things up in the
ceval loop instead.

This is safe because the compiler only sets __annotate__ to just-created dedicated
annotate functions.

Include/internal/pycore_opcode_metadata.h
Include/internal/pycore_uop_metadata.h
Lib/test/test_type_annotations.py
Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-07-21-30.gh-issue-137814.6yRTeu.rst [new file with mode: 0644]
Python/bytecodes.c
Python/executor_cases.c.h
Python/generated_cases.c.h

index ccd10e90f0c0a35780646920674e89887e465aa7..90634414df1307b345b7a238e1b78b61da0afc50 100644 (file)
@@ -1271,7 +1271,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
     [SEND_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG },
     [SETUP_ANNOTATIONS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [SET_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
-    [SET_FUNCTION_ATTRIBUTE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG },
+    [SET_FUNCTION_ATTRIBUTE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [SET_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [STORE_ATTR] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC000, HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
index 2166a6900f120a193e227cd893b1cf71ac38c70c..9c920c743cc2e0ce61106bfd950b20305edb1a98 100644 (file)
@@ -277,7 +277,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
     [_CALL_KW_NON_PY] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
     [_MAKE_CALLARGS_A_TUPLE] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
     [_MAKE_FUNCTION] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
-    [_SET_FUNCTION_ATTRIBUTE] = HAS_ARG_FLAG,
+    [_SET_FUNCTION_ATTRIBUTE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
     [_RETURN_GENERATOR] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
     [_BUILD_SLICE] = HAS_ARG_FLAG | HAS_ERROR_FLAG,
     [_CONVERT_VALUE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
index 4c58fade1b4f26e4683ef4f4304e7e3090134268..2a67d63cde66d5a1150fe0965b275e2b753717c6 100644 (file)
@@ -836,6 +836,23 @@ class RegressionTests(unittest.TestCase):
         lamb = list(genexp)[0]
         self.assertEqual(lamb(), 42)
 
+    def test_annotate_qualname(self):
+        code = """
+        def f() -> None:
+            def nested() -> None: pass
+            return nested
+        class Outer:
+            x: int
+            def method(self, x: int):
+                pass
+        """
+        ns = run_code(code)
+        method = ns["Outer"].method
+        self.assertEqual(ns["f"].__annotate__.__qualname__, "f.__annotate__")
+        self.assertEqual(ns["f"]().__annotate__.__qualname__, "f.<locals>.nested.__annotate__")
+        self.assertEqual(method.__annotate__.__qualname__, "Outer.method.__annotate__")
+        self.assertEqual(ns["Outer"].__annotate__.__qualname__, "Outer.__annotate__")
+
     # gh-138349
     def test_module_level_annotation_plus_listcomp(self):
         cases = [
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-07-21-30.gh-issue-137814.6yRTeu.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-07-21-30.gh-issue-137814.6yRTeu.rst
new file mode 100644 (file)
index 0000000..8356131
--- /dev/null
@@ -0,0 +1,2 @@
+Fix the ``__qualname__`` attribute of ``__annotate__`` functions on
+functions.
index a477fdd51ec5a58463194f7fd310bcbcfe34e02d..32de9229ebfeefb3aaa0dea37eb801351006b62b 100644 (file)
@@ -4970,6 +4970,13 @@ dummy_func(
             PyObject **ptr = (PyObject **)(((char *)func) + offset);
             assert(*ptr == NULL);
             *ptr = attr;
+            if (oparg == MAKE_FUNCTION_ANNOTATE && PyFunction_Check(attr)) {
+                // gh-137814: Fix the qualname of __annotate__ functions
+                PyFunctionObject *func_obj = (PyFunctionObject *)attr;
+                PyObject *fixed_qualname = PyUnicode_FromFormat("%U.__annotate__", ((PyFunctionObject *)func)->func_qualname);
+                ERROR_IF(fixed_qualname == NULL);
+                Py_SETREF(func_obj->func_qualname, fixed_qualname);
+            }
         }
 
         inst(RETURN_GENERATOR, (-- res)) {
index 552cfac2dbef28e31de306fccceba9983571c0f2..84d3ea501593cae9b1b12d1595584d34d3870f7a 100644 (file)
             PyObject **ptr = (PyObject **)(((char *)func) + offset);
             assert(*ptr == NULL);
             *ptr = attr;
+            if (oparg == MAKE_FUNCTION_ANNOTATE && PyFunction_Check(attr)) {
+                PyFunctionObject *func_obj = (PyFunctionObject *)attr;
+                stack_pointer[-2] = func_out;
+                stack_pointer += -1;
+                assert(WITHIN_STACK_BOUNDS());
+                _PyFrame_SetStackPointer(frame, stack_pointer);
+                PyObject *fixed_qualname = PyUnicode_FromFormat("%U.__annotate__", ((PyFunctionObject *)func)->func_qualname);
+                stack_pointer = _PyFrame_GetStackPointer(frame);
+                if (fixed_qualname == NULL) {
+                    JUMP_TO_ERROR();
+                }
+                _PyFrame_SetStackPointer(frame, stack_pointer);
+                Py_SETREF(func_obj->func_qualname, fixed_qualname);
+                stack_pointer = _PyFrame_GetStackPointer(frame);
+                stack_pointer += 1;
+            }
             stack_pointer[-2] = func_out;
             stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
index 5cba25d5c7d3852b1c63a3318eef47f768b4cf67..e06d747f17b8a97a6068403e73111217a210869f 100644 (file)
             PyObject **ptr = (PyObject **)(((char *)func) + offset);
             assert(*ptr == NULL);
             *ptr = attr;
+            if (oparg == MAKE_FUNCTION_ANNOTATE && PyFunction_Check(attr)) {
+                PyFunctionObject *func_obj = (PyFunctionObject *)attr;
+                stack_pointer[-2] = func_out;
+                stack_pointer += -1;
+                assert(WITHIN_STACK_BOUNDS());
+                _PyFrame_SetStackPointer(frame, stack_pointer);
+                PyObject *fixed_qualname = PyUnicode_FromFormat("%U.__annotate__", ((PyFunctionObject *)func)->func_qualname);
+                stack_pointer = _PyFrame_GetStackPointer(frame);
+                if (fixed_qualname == NULL) {
+                    JUMP_TO_LABEL(error);
+                }
+                _PyFrame_SetStackPointer(frame, stack_pointer);
+                Py_SETREF(func_obj->func_qualname, fixed_qualname);
+                stack_pointer = _PyFrame_GetStackPointer(frame);
+                stack_pointer += 1;
+            }
             stack_pointer[-2] = func_out;
             stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());