From: Jelle Zijlstra Date: Thu, 16 Apr 2026 04:52:43 +0000 (-0700) Subject: [3.14] gh-137814: Fix __qualname__ of __annotate__ functions in the interpreter ... X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=1c9de6bbaab9d909a5f0de7fe6eb19dcce00b305;p=thirdparty%2FPython%2Fcpython.git [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. --- diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index ccd10e90f0c0..90634414df13 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 2166a6900f12..9c920c743cc2 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 4c58fade1b4f..2a67d63cde66 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -836,6 +836,23 @@ class RegressionTests(unittest.TestCase): 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 000000000000..83561312deeb --- /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 a477fdd51ec5..32de9229ebfe 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 552cfac2dbef..84d3ea501593 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 5cba25d5c7d3..e06d747f17b8 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());