https://github.com/python/cpython/commit/1c9de6bbaab9d909a5f0de7fe6eb19dcce00b305
commit: 1c9de6bbaab9d909a5f0de7fe6eb19dcce00b305
branch: 3.14
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-04-15T21:52:43-07:00
summary:

[3.14] gh-137814: Fix __qualname__ of __annotate__ functions in the interpreter 
(#148221)

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.

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-07-21-30.gh-issue-137814.6yRTeu.rst
M Include/internal/pycore_opcode_metadata.h
M Include/internal/pycore_uop_metadata.h
M Lib/test/test_type_annotations.py
M Python/bytecodes.c
M Python/executor_cases.c.h
M Python/generated_cases.c.h

diff --git a/Include/internal/pycore_opcode_metadata.h 
b/Include/internal/pycore_opcode_metadata.h
index ccd10e90f0c0a3..90634414df1307 100644
--- a/Include/internal/pycore_opcode_metadata.h
+++ b/Include/internal/pycore_opcode_metadata.h
@@ -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 },
diff --git a/Include/internal/pycore_uop_metadata.h 
b/Include/internal/pycore_uop_metadata.h
index 2166a6900f120a..9c920c743cc2e0 100644
--- a/Include/internal/pycore_uop_metadata.h
+++ b/Include/internal/pycore_uop_metadata.h
@@ -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,
diff --git a/Lib/test/test_type_annotations.py 
b/Lib/test/test_type_annotations.py
index 4c58fade1b4f26..2a67d63cde66d5 100644
--- a/Lib/test/test_type_annotations.py
+++ b/Lib/test/test_type_annotations.py
@@ -836,6 +836,23 @@ def test_complex_comprehension_inlining_exec(self):
         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
index 00000000000000..83561312deeb02
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-07-21-30.gh-issue-137814.6yRTeu.rst
@@ -0,0 +1,2 @@
+Fix the ``__qualname__`` attribute of ``__annotate__`` functions on
+functions.
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index a477fdd51ec5a5..32de9229ebfeef 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -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)) {
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 552cfac2dbef28..84d3ea501593ca 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -6615,6 +6615,22 @@
             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());
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index 5cba25d5c7d385..e06d747f17b8a9 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -10893,6 +10893,22 @@
             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());

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to