From 619f5be96a4d0a67d3481abb05b61fe0d0c0da47 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 7 Apr 2026 07:21:40 -0700 Subject: [PATCH] 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 | 2 +- Include/internal/pycore_uop_metadata.h | 2 +- Lib/test/test_type_annotations.py | 17 +++++++++++++++++ ...26-04-07-07-21-30.gh-issue-137814.6yRTeu.rst | 2 ++ Python/bytecodes.c | 7 +++++++ Python/executor_cases.c.h | 16 ++++++++++++++++ Python/generated_cases.c.h | 16 ++++++++++++++++ 7 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-07-21-30.gh-issue-137814.6yRTeu.rst 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..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());