https://github.com/python/cpython/commit/5b8cd314e24074505fa4da57a06a7a9d46bc2434
commit: 5b8cd314e24074505fa4da57a06a7a9d46bc2434
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-04-15T21:52:30-07:00
summary:

gh-137814: Fix `__qualname__` of `__annotate__` (#137842)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-08-15-21-33-16.gh-issue-137814.6yRTeu.rst
M Include/internal/pycore_magic_number.h
M Include/internal/pycore_symtable.h
M Lib/test/test_type_annotations.py
M Python/compile.c
M Python/symtable.c

diff --git a/Include/internal/pycore_magic_number.h 
b/Include/internal/pycore_magic_number.h
index 9d36165c8a8ffb..c6a2e72fe7b0db 100644
--- a/Include/internal/pycore_magic_number.h
+++ b/Include/internal/pycore_magic_number.h
@@ -294,6 +294,7 @@ Known values:
     Python 3.15a4 3661 (Lazy imports IMPORT_NAME opcode changes)
     Python 3.15a8 3662 (Add counter to RESUME)
     Python 3.15a8 3663 (Merge GET_ITER and GET_YIELD_FROM_ITER. Modify SEND to 
make it a bit more like FOR_ITER)
+    Python 3.15a8 3664 (Fix __qualname__ for __annotate__ functions)
 
 
     Python 3.16 will start with 3700
@@ -307,7 +308,7 @@ PC/launcher.c must also be updated.
 
 */
 
-#define PYC_MAGIC_NUMBER 3663
+#define PYC_MAGIC_NUMBER 3664
 /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
    (little-endian) and then appending b'\r\n'. */
 #define PYC_MAGIC_NUMBER_TOKEN \
diff --git a/Include/internal/pycore_symtable.h 
b/Include/internal/pycore_symtable.h
index e1176c65c5ca9e..c650a94a1eab2e 100644
--- a/Include/internal/pycore_symtable.h
+++ b/Include/internal/pycore_symtable.h
@@ -90,6 +90,7 @@ typedef struct _symtable_entry {
     PyObject *ste_id;        /* int: key in ste_table->st_blocks */
     PyObject *ste_symbols;   /* dict: variable names to flags */
     PyObject *ste_name;      /* string: name of current block */
+    PyObject *ste_function_name;  /* string or NULL: for annotation blocks: 
name of the corresponding functions */
     PyObject *ste_varnames;  /* list of function parameters */
     PyObject *ste_children;  /* list of child blocks */
     PyObject *ste_directives;/* locations of global and nonlocal statements */
diff --git a/Lib/test/test_type_annotations.py 
b/Lib/test/test_type_annotations.py
index d40eb382c536d9..d459f497e333e6 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(method.__annotate__.__qualname__, 
"Outer.method.__annotate__")
+        self.assertEqual(ns["f"].__annotate__.__qualname__, "f.__annotate__")
+        self.assertEqual(ns["f"]().__annotate__.__qualname__, 
"f.<locals>.nested.__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/2025-08-15-21-33-16.gh-issue-137814.6yRTeu.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-15-21-33-16.gh-issue-137814.6yRTeu.rst
new file mode 100644
index 00000000000000..83561312deeb02
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-15-21-33-16.gh-issue-137814.6yRTeu.rst
@@ -0,0 +1,2 @@
+Fix the ``__qualname__`` attribute of ``__annotate__`` functions on
+functions.
diff --git a/Python/compile.c b/Python/compile.c
index 365b118cc71b44..5f82641a3948c6 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -297,6 +297,19 @@ compiler_set_qualname(compiler *c)
                 base = Py_NewRef(parent->u_metadata.u_qualname);
             }
         }
+        if (u->u_ste->ste_function_name != NULL) {
+            PyObject *tmp = base;
+            base = PyUnicode_FromFormat("%U.%U",
+                base,
+                u->u_ste->ste_function_name);
+            Py_DECREF(tmp);
+            if (base == NULL) {
+                return ERROR;
+            }
+        }
+    }
+    else if (u->u_ste->ste_function_name != NULL) {
+        base = Py_NewRef(u->u_ste->ste_function_name);
     }
 
     if (base != NULL) {
diff --git a/Python/symtable.c b/Python/symtable.c
index 4b695e4b2588d8..2263a2d8db9097 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -108,6 +108,7 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty 
block,
     ste->ste_id = k; /* ste owns reference to k */
 
     ste->ste_name = Py_NewRef(name);
+    ste->ste_function_name = NULL;
 
     ste->ste_symbols = NULL;
     ste->ste_varnames = NULL;
@@ -186,6 +187,7 @@ ste_dealloc(PyObject *op)
     ste->ste_table = NULL;
     Py_XDECREF(ste->ste_id);
     Py_XDECREF(ste->ste_name);
+    Py_XDECREF(ste->ste_function_name);
     Py_XDECREF(ste->ste_symbols);
     Py_XDECREF(ste->ste_varnames);
     Py_XDECREF(ste->ste_children);
@@ -2918,6 +2920,7 @@ symtable_visit_annotations(struct symtable *st, stmt_ty 
o, arguments_ty a, expr_
                               (void *)a, LOCATION(o))) {
         return 0;
     }
+    Py_XSETREF(st->st_cur->ste_function_name, 
Py_NewRef(function_ste->ste_name));
     if (is_in_class || current_type == ClassBlock) {
         st->st_cur->ste_can_see_class_scope = 1;
         if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(o))) {

_______________________________________________
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