diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 98099b4a497b01..91455f6e23244b 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..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 e2f1c7e8eb5bce..73f69e76a85a89 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -290,6 +290,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 bcd7365f8e1f14..dd4701354cbbc1 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; @@ -185,6 +186,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); @@ -2830,6 +2832,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))) {