From 1c9de6bbaab9d909a5f0de7fe6eb19dcce00b305 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 15 Apr 2026 21:52:43 -0700 Subject: [PATCH] [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. --- 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 ccd10e90f0c..90634414df1 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 2166a6900f1..9c920c743cc 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 4c58fade1b4..2a67d63cde6 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 00000000000..83561312dee --- /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 a477fdd51ec..32de9229ebf 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 552cfac2dbe..84d3ea50159 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 5cba25d5c7d..e06d747f17b 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());