From 7aa353c414509e6881c61acc93cb504a611b0b40 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 12 Dec 2025 14:10:25 +0100 Subject: [PATCH 1/7] gh-142217: Deprecate the private _Py_Identifier C API (#142221) Deprecate functions: * _PyObject_CallMethodId() * _PyObject_GetAttrId() * _PyUnicode_FromId() --- Doc/deprecations/c-api-pending-removal-in-3.20.rst | 7 +++++++ Doc/whatsnew/3.15.rst | 7 +++++++ Include/cpython/abstract.h | 2 +- Include/cpython/object.h | 2 +- Include/cpython/unicodeobject.h | 2 +- .../C_API/2025-12-03-14-41-07.gh-issue-141049.VuAUe2.rst | 5 +++++ Objects/call.c | 3 +++ Objects/object.c | 3 +++ 8 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2025-12-03-14-41-07.gh-issue-141049.VuAUe2.rst diff --git a/Doc/deprecations/c-api-pending-removal-in-3.20.rst b/Doc/deprecations/c-api-pending-removal-in-3.20.rst index 18623b19a2ab8d..a813cb21dd4dbf 100644 --- a/Doc/deprecations/c-api-pending-removal-in-3.20.rst +++ b/Doc/deprecations/c-api-pending-removal-in-3.20.rst @@ -1,6 +1,13 @@ Pending removal in Python 3.20 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* :c:func:`!_PyObject_CallMethodId`, :c:func:`!_PyObject_GetAttrId` and + :c:func:`!_PyUnicode_FromId` are deprecated since 3.15 and will be removed in + 3.20. Instead, use :c:func:`PyUnicode_FromString()` and cache the result in + the module state, then call :c:func:`PyObject_CallMethod` or + :c:func:`PyObject_GetAttr`. + (Contributed by Victor Stinner in :gh:`141049`.) + * The ``cval`` field in :c:type:`PyComplexObject` (:gh:`128813`). Use :c:func:`PyComplex_AsCComplex` and :c:func:`PyComplex_FromCComplex` to convert a Python complex number to/from the C :c:type:`Py_complex` diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 9d4686f982a99a..1d8b838cd2e7f0 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1210,6 +1210,13 @@ Deprecated C APIs use the :c:type:`PyBytesWriter` API instead. (Contributed by Victor Stinner in :gh:`129813`.) +* :c:func:`!_PyObject_CallMethodId`, :c:func:`!_PyObject_GetAttrId` and + :c:func:`!_PyUnicode_FromId` are deprecated since 3.15 and will be removed in + 3.20. Instead, use :c:func:`PyUnicode_FromString()` and cache the result in + the module state, then call :c:func:`PyObject_CallMethod` or + :c:func:`PyObject_GetAttr`. + (Contributed by Victor Stinner in :gh:`141049`.) + * Deprecate :c:member:`~PyComplexObject.cval` field of the :c:type:`PyComplexObject` type. Use :c:func:`PyComplex_AsCComplex` and :c:func:`PyComplex_FromCComplex` diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h index ffd19ccd3500fa..7490ece52e5220 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -6,7 +6,7 @@ /* Like PyObject_CallMethod(), but expect a _Py_Identifier* as the method name. */ -PyAPI_FUNC(PyObject*) _PyObject_CallMethodId( +Py_DEPRECATED(3.15) PyAPI_FUNC(PyObject*) _PyObject_CallMethodId( PyObject *obj, _Py_Identifier *name, const char *format, ...); diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 8693390aeda624..85d5edd62e3a72 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -300,7 +300,7 @@ PyAPI_FUNC(void) PyUnstable_Object_Dump(PyObject *); // Alias for backward compatibility #define _PyObject_Dump PyUnstable_Object_Dump -PyAPI_FUNC(PyObject*) _PyObject_GetAttrId(PyObject *, _Py_Identifier *); +Py_DEPRECATED(3.15) PyAPI_FUNC(PyObject*) _PyObject_GetAttrId(PyObject *, _Py_Identifier *); PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *); PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *); diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 2853d24c34b66e..631a6570658410 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -778,4 +778,4 @@ static inline int Py_UNICODE_ISALNUM(Py_UCS4 ch) { // Return an interned Unicode object for an Identifier; may fail if there is no // memory. -PyAPI_FUNC(PyObject*) _PyUnicode_FromId(_Py_Identifier*); +Py_DEPRECATED(3.15) PyAPI_FUNC(PyObject*) _PyUnicode_FromId(_Py_Identifier*); diff --git a/Misc/NEWS.d/next/C_API/2025-12-03-14-41-07.gh-issue-141049.VuAUe2.rst b/Misc/NEWS.d/next/C_API/2025-12-03-14-41-07.gh-issue-141049.VuAUe2.rst new file mode 100644 index 00000000000000..b0fcd24d414270 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-12-03-14-41-07.gh-issue-141049.VuAUe2.rst @@ -0,0 +1,5 @@ +:c:func:`!_PyObject_CallMethodId`, :c:func:`!_PyObject_GetAttrId` and +:c:func:`!_PyUnicode_FromId` are deprecated since 3.15 and will be removed in +3.20. Instead, use :c:func:`PyUnicode_FromString()` and cache the result in +the module state, then call :c:func:`PyObject_CallMethod` or +:c:func:`PyObject_GetAttr`. Patch by Victor Stinner. diff --git a/Objects/call.c b/Objects/call.c index c69015abfb3ed5..41d075caf11ce6 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -708,7 +708,10 @@ _PyObject_CallMethodId(PyObject *obj, _Py_Identifier *name, return null_error(tstate); } +_Py_COMP_DIAG_PUSH +_Py_COMP_DIAG_IGNORE_DEPR_DECLS PyObject *callable = _PyObject_GetAttrId(obj, name); +_Py_COMP_DIAG_POP if (callable == NULL) { return NULL; } diff --git a/Objects/object.c b/Objects/object.c index 36a37bb0bbea4d..4fc692bb02940e 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1263,7 +1263,10 @@ PyObject * _PyObject_GetAttrId(PyObject *v, _Py_Identifier *name) { PyObject *result; +_Py_COMP_DIAG_PUSH +_Py_COMP_DIAG_IGNORE_DEPR_DECLS PyObject *oname = _PyUnicode_FromId(name); /* borrowed */ +_Py_COMP_DIAG_POP if (!oname) return NULL; result = PyObject_GetAttr(v, oname); From e0bca091a4f112b9f0beb4ce19dfac2a7fc50342 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 12 Dec 2025 14:12:11 +0100 Subject: [PATCH 2/7] gh-142627: Ignore anonymous mappings in Linux remote debugging (#142628) --- Python/remote_debug.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 1c02870d3af475..d3932a3fd1e4d6 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -722,6 +722,11 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c } const char *path = line + path_pos; + if (path[0] == '[' && path[strlen(path)-1] == ']') { + // Skip [heap], [stack], [anon:cpython:pymalloc], etc. + continue; + } + const char *filename = strrchr(path, '/'); if (filename) { filename++; // Move past the '/' From a3a611b0429289d5531b90e0a95793ab5a61d967 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Fri, 12 Dec 2025 22:04:11 +0800 Subject: [PATCH 3/7] gh-134584: Revert partially GH-135860 (GH-142620) --- Include/internal/pycore_opcode_metadata.h | 2 +- Include/internal/pycore_uop_ids.h | 2 +- Include/internal/pycore_uop_metadata.h | 6 ++--- Lib/test/test_capi/test_opt.py | 3 ++- Python/bytecodes.c | 11 ++------- Python/executor_cases.c.h | 17 +++++++------ Python/generated_cases.c.h | 30 +++++++---------------- Python/opcode_targets.h | 1 - Python/optimizer_bytecodes.c | 3 +-- Python/optimizer_cases.c.h | 7 ++---- 10 files changed, 31 insertions(+), 51 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 35659de22b799d..13e58721ebee9e 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1376,7 +1376,7 @@ _PyOpcode_macro_expansion[256] = { [CALL_PY_EXACT_ARGS] = { .nuops = 8, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _CHECK_FUNCTION_VERSION, 2, 1 }, { _CHECK_FUNCTION_EXACT_ARGS, OPARG_SIMPLE, 3 }, { _CHECK_STACK_SPACE, OPARG_SIMPLE, 3 }, { _CHECK_RECURSION_REMAINING, OPARG_SIMPLE, 3 }, { _INIT_CALL_PY_EXACT_ARGS, OPARG_SIMPLE, 3 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 3 }, { _PUSH_FRAME, OPARG_SIMPLE, 3 } } }, [CALL_PY_GENERAL] = { .nuops = 6, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _CHECK_FUNCTION_VERSION, 2, 1 }, { _CHECK_RECURSION_REMAINING, OPARG_SIMPLE, 3 }, { _PY_FRAME_GENERAL, OPARG_SIMPLE, 3 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 3 }, { _PUSH_FRAME, OPARG_SIMPLE, 3 } } }, [CALL_STR_1] = { .nuops = 4, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_STR_1, OPARG_SIMPLE, 3 }, { _CALL_STR_1, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } }, - [CALL_TUPLE_1] = { .nuops = 5, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_TUPLE_1, OPARG_SIMPLE, 3 }, { _CALL_TUPLE_1, OPARG_SIMPLE, 3 }, { _POP_TOP, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } }, + [CALL_TUPLE_1] = { .nuops = 4, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_TUPLE_1, OPARG_SIMPLE, 3 }, { _CALL_TUPLE_1, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } }, [CALL_TYPE_1] = { .nuops = 3, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_TYPE_1, OPARG_SIMPLE, 3 }, { _CALL_TYPE_1, OPARG_SIMPLE, 3 } } }, [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { _CHECK_EG_MATCH, OPARG_SIMPLE, 0 } } }, [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { _CHECK_EXC_MATCH, OPARG_SIMPLE, 0 } } }, diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index ce1ad5a4c8a5f6..56928b06685270 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -439,7 +439,7 @@ extern "C" { #define _CALL_METHOD_DESCRIPTOR_O_r01 632 #define _CALL_NON_PY_GENERAL_r01 633 #define _CALL_STR_1_r31 634 -#define _CALL_TUPLE_1_r32 635 +#define _CALL_TUPLE_1_r31 635 #define _CALL_TYPE_1_r31 636 #define _CHECK_AND_ALLOCATE_OBJECT_r00 637 #define _CHECK_ATTR_CLASS_r01 638 diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 1e74588d3aa62e..2ae84bdee0df12 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -2543,7 +2543,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { { -1, -1, -1 }, { -1, -1, -1 }, { -1, -1, -1 }, - { 2, 0, _CALL_TUPLE_1_r32 }, + { 1, 0, _CALL_TUPLE_1_r31 }, }, }, [_CHECK_AND_ALLOCATE_OBJECT] = { @@ -3739,7 +3739,7 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_GUARD_CALLABLE_TUPLE_1_r13] = _GUARD_CALLABLE_TUPLE_1, [_GUARD_CALLABLE_TUPLE_1_r23] = _GUARD_CALLABLE_TUPLE_1, [_GUARD_CALLABLE_TUPLE_1_r33] = _GUARD_CALLABLE_TUPLE_1, - [_CALL_TUPLE_1_r32] = _CALL_TUPLE_1, + [_CALL_TUPLE_1_r31] = _CALL_TUPLE_1, [_CHECK_AND_ALLOCATE_OBJECT_r00] = _CHECK_AND_ALLOCATE_OBJECT, [_CREATE_INIT_FRAME_r01] = _CREATE_INIT_FRAME, [_EXIT_INIT_CHECK_r10] = _EXIT_INIT_CHECK, @@ -4058,7 +4058,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_CALL_STR_1] = "_CALL_STR_1", [_CALL_STR_1_r31] = "_CALL_STR_1_r31", [_CALL_TUPLE_1] = "_CALL_TUPLE_1", - [_CALL_TUPLE_1_r32] = "_CALL_TUPLE_1_r32", + [_CALL_TUPLE_1_r31] = "_CALL_TUPLE_1_r31", [_CALL_TYPE_1] = "_CALL_TYPE_1", [_CALL_TYPE_1_r31] = "_CALL_TYPE_1_r31", [_CHECK_AND_ALLOCATE_OBJECT] = "_CHECK_AND_ALLOCATE_OBJECT", diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 0e7fd62c28a065..e512c08752a02a 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1938,7 +1938,8 @@ def testfunc(n): self.assertIsNotNone(ex) uops = get_opnames(ex) self.assertIn("_CALL_TUPLE_1", uops) - self.assertIn("_POP_TOP_NOP", uops) + # Re-enable later gh-134584 + # self.assertIn("_POP_TOP_NOP", uops) def test_call_str_1(self): def testfunc(n): diff --git a/Python/bytecodes.c b/Python/bytecodes.c index cb39a23d951077..3e7b7d5594f8f7 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4068,13 +4068,13 @@ dummy_func( DEOPT_IF(callable_o != (PyObject *)&PyTuple_Type); } - op(_CALL_TUPLE_1, (callable, null, arg -- res, a)) { + op(_CALL_TUPLE_1, (callable, null, arg -- res)) { PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg); assert(oparg == 1); STAT_INC(CALL, hit); PyObject *res_o = PySequence_Tuple(arg_o); - a = arg; + PyStackRef_CLOSE(arg); INPUTS_DEAD(); ERROR_IF(res_o == NULL); res = PyStackRef_FromPyObjectSteal(res_o); @@ -4086,7 +4086,6 @@ dummy_func( _GUARD_NOS_NULL + _GUARD_CALLABLE_TUPLE_1 + _CALL_TUPLE_1 + - POP_TOP + _CHECK_PERIODIC_AT_END; op(_CHECK_AND_ALLOCATE_OBJECT, (type_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { @@ -5454,12 +5453,6 @@ dummy_func( } } - label(pop_3_error) { - stack_pointer -= 3; - assert(WITHIN_STACK_BOUNDS()); - goto error; - } - label(pop_2_error) { stack_pointer -= 2; assert(WITHIN_STACK_BOUNDS()); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 598fea4dfbf692..9c2bffb35abbfb 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -12888,12 +12888,11 @@ break; } - case _CALL_TUPLE_1_r32: { + case _CALL_TUPLE_1_r31: { CHECK_CURRENT_CACHED_VALUES(3); assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); _PyStackRef arg; _PyStackRef res; - _PyStackRef a; _PyStackRef _stack_item_0 = _tos_cache0; _PyStackRef _stack_item_1 = _tos_cache1; _PyStackRef _stack_item_2 = _tos_cache2; @@ -12910,19 +12909,23 @@ _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = PySequence_Tuple(arg_o); stack_pointer = _PyFrame_GetStackPointer(frame); - a = arg; + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(arg); + stack_pointer = _PyFrame_GetStackPointer(frame); if (res_o == NULL) { - stack_pointer += -3; + stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } res = PyStackRef_FromPyObjectSteal(res_o); - _tos_cache1 = a; _tos_cache0 = res; + _tos_cache1 = PyStackRef_ZERO_BITS; _tos_cache2 = PyStackRef_ZERO_BITS; - SET_CURRENT_CACHED_VALUES(2); - stack_pointer += -3; + SET_CURRENT_CACHED_VALUES(1); + stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); break; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index a0e6fd2d7cba27..e72c621f10d340 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -4040,8 +4040,6 @@ _PyStackRef callable; _PyStackRef arg; _PyStackRef res; - _PyStackRef a; - _PyStackRef value; /* Skip 1 cache entry */ /* Skip 2 cache entries */ // _GUARD_NOS_NULL @@ -4072,24 +4070,21 @@ _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = PySequence_Tuple(arg_o); stack_pointer = _PyFrame_GetStackPointer(frame); - a = arg; - if (res_o == NULL) { - JUMP_TO_LABEL(pop_3_error); - } - res = PyStackRef_FromPyObjectSteal(res_o); - } - // _POP_TOP - { - value = a; - stack_pointer[-3] = res; - stack_pointer += -2; + stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_XCLOSE(value); + PyStackRef_CLOSE(arg); stack_pointer = _PyFrame_GetStackPointer(frame); + if (res_o == NULL) { + JUMP_TO_LABEL(pop_2_error); + } + res = PyStackRef_FromPyObjectSteal(res_o); } // _CHECK_PERIODIC_AT_END { + stack_pointer[-2] = res; + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); int err = check_periodics(tstate); stack_pointer = _PyFrame_GetStackPointer(frame); @@ -11863,13 +11858,6 @@ JUMP_TO_LABEL(error); #endif /* _Py_TAIL_CALL_INTERP */ /* BEGIN LABELS */ - LABEL(pop_3_error) - { - stack_pointer -= 3; - assert(WITHIN_STACK_BOUNDS()); - JUMP_TO_LABEL(error); - } - LABEL(pop_2_error) { stack_pointer -= 2; diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index 1a8a4392187eca..b2fa7d01e8f6c2 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -522,7 +522,6 @@ static py_tail_call_funcptr instruction_funcptr_handler_table[256]; static py_tail_call_funcptr instruction_funcptr_tracing_table[256]; -Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_pop_3_error(TAIL_CALL_PARAMS); Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_pop_2_error(TAIL_CALL_PARAMS); Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_pop_1_error(TAIL_CALL_PARAMS); Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_error(TAIL_CALL_PARAMS); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 9e767464500d45..61f2dd1e454148 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -1097,7 +1097,7 @@ dummy_func(void) { } } - op(_CALL_TUPLE_1, (callable, null, arg -- res, a)) { + op(_CALL_TUPLE_1, (callable, null, arg -- res)) { if (sym_matches_type(arg, &PyTuple_Type)) { // e.g. tuple((1, 2)) or tuple(foo) where foo is known to be a tuple // Note: we must strip the reference information because it goes @@ -1107,7 +1107,6 @@ dummy_func(void) { else { res = sym_new_type(ctx, &PyTuple_Type); } - a = arg; } op(_GUARD_TOS_LIST, (tos -- tos)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index c3f122215ed7bd..7d95bce7679815 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2882,7 +2882,6 @@ case _CALL_TUPLE_1: { JitOptRef arg; JitOptRef res; - JitOptRef a; arg = stack_pointer[-1]; if (sym_matches_type(arg, &PyTuple_Type)) { res = PyJitRef_StripReferenceInfo(arg); @@ -2890,11 +2889,9 @@ else { res = sym_new_type(ctx, &PyTuple_Type); } - a = arg; - CHECK_STACK_BOUNDS(-1); + CHECK_STACK_BOUNDS(-2); stack_pointer[-3] = res; - stack_pointer[-2] = a; - stack_pointer += -1; + stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); break; } From f564654bae79c69e3de40dadbec2399f1423ffcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Fri, 12 Dec 2025 15:42:38 +0100 Subject: [PATCH 4/7] gh-142353: Isolate tests from personal GNU Readline init files (#142370) Isolate tests from personal Readline init files using `INPUTRC=/dev/null` trick. Co-authored-by: Victor Stinner --- Lib/test/support/pty_helper.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/support/pty_helper.py b/Lib/test/support/pty_helper.py index 6587fd40333c51..dbe7fa429096fc 100644 --- a/Lib/test/support/pty_helper.py +++ b/Lib/test/support/pty_helper.py @@ -15,6 +15,14 @@ def run_pty(script, input=b"dummy input\r", env=None): output = bytearray() [master, slave] = pty.openpty() args = (sys.executable, '-c', script) + + # Isolate readline from personal init files by setting INPUTRC + # to an empty file. See also GH-142353. + if env is None: + env = {**os.environ.copy(), "INPUTRC": os.devnull} + else: + env.setdefault("INPUTRC", os.devnull) + proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env) os.close(slave) with ExitStack() as cleanup: From 340a6846743e4a861f3d7bd62d2ae428f40dfa22 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:06:28 +0000 Subject: [PATCH 5/7] gh-138122: Add docs button to Tachyon heatmap and flamegraph (#142614) --- .../sampling/_flamegraph_assets/flamegraph.js | 6 ++-- .../flamegraph_template.html | 31 +++++++++++++++++-- .../sampling/_heatmap_assets/heatmap.js | 6 ++-- .../sampling/_heatmap_assets/heatmap_index.js | 6 ++-- .../heatmap_index_template.html | 19 +++++++++++- .../heatmap_pyfile_template.html | 25 +++++++++++++-- Lib/profiling/sampling/heatmap_collector.py | 2 ++ Lib/profiling/sampling/stack_collector.py | 4 +++ 8 files changed, 87 insertions(+), 12 deletions(-) diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js index fb81094521815e..6345320bd2555d 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js @@ -92,7 +92,8 @@ function toggleTheme() { // Update theme button icon const btn = document.getElementById('theme-btn'); if (btn) { - btn.innerHTML = next === 'dark' ? '☼' : '☾'; // sun or moon + btn.querySelector('.icon-moon').style.display = next === 'dark' ? 'none' : ''; + btn.querySelector('.icon-sun').style.display = next === 'dark' ? '' : 'none'; } // Re-render flamegraph with new theme colors @@ -160,7 +161,8 @@ function restoreUIState() { document.documentElement.setAttribute('data-theme', savedTheme); const btn = document.getElementById('theme-btn'); if (btn) { - btn.innerHTML = savedTheme === 'dark' ? '☼' : '☾'; + btn.querySelector('.icon-moon').style.display = savedTheme === 'dark' ? 'none' : ''; + btn.querySelector('.icon-sun').style.display = savedTheme === 'dark' ? '' : 'none'; } } diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html b/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html index 211296a708643f..02855563f83f7c 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html @@ -39,18 +39,43 @@ class="toolbar-btn" onclick="resetZoom()" title="Reset zoom" - >⌂ + > + + + + + > + + + + + + + + + + > + + + + + diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap.js b/Lib/profiling/sampling/_heatmap_assets/heatmap.js index 038aa44b3df619..8ac4ef43e53b37 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap.js +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap.js @@ -24,7 +24,8 @@ function toggleTheme() { // Update theme button icon const btn = document.getElementById('theme-btn'); if (btn) { - btn.innerHTML = next === 'dark' ? '☼' : '☾'; // sun or moon + btn.querySelector('.icon-moon').style.display = next === 'dark' ? 'none' : ''; + btn.querySelector('.icon-sun').style.display = next === 'dark' ? '' : 'none'; } applyLineColors(); @@ -39,7 +40,8 @@ function restoreUIState() { document.documentElement.setAttribute('data-theme', savedTheme); const btn = document.getElementById('theme-btn'); if (btn) { - btn.innerHTML = savedTheme === 'dark' ? '☼' : '☾'; + btn.querySelector('.icon-moon').style.display = savedTheme === 'dark' ? 'none' : ''; + btn.querySelector('.icon-sun').style.display = savedTheme === 'dark' ? '' : 'none'; } } } diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap_index.js b/Lib/profiling/sampling/_heatmap_assets/heatmap_index.js index 4ddacca5173d34..8eb6af0db5335e 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap_index.js +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap_index.js @@ -28,7 +28,8 @@ function toggleTheme() { // Update theme button icon const btn = document.getElementById('theme-btn'); if (btn) { - btn.innerHTML = next === 'dark' ? '☼' : '☾'; // sun or moon + btn.querySelector('.icon-moon').style.display = next === 'dark' ? 'none' : ''; + btn.querySelector('.icon-sun').style.display = next === 'dark' ? '' : 'none'; } applyHeatmapBarColors(); @@ -41,7 +42,8 @@ function restoreUIState() { document.documentElement.setAttribute('data-theme', savedTheme); const btn = document.getElementById('theme-btn'); if (btn) { - btn.innerHTML = savedTheme === 'dark' ? '☼' : '☾'; + btn.querySelector('.icon-moon').style.display = savedTheme === 'dark' ? 'none' : ''; + btn.querySelector('.icon-sun').style.display = savedTheme === 'dark' ? '' : 'none'; } } } diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap_index_template.html b/Lib/profiling/sampling/_heatmap_assets/heatmap_index_template.html index 98996bdbf5ffb1..3620f8efb8058a 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap_index_template.html +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap_index_template.html @@ -17,12 +17,29 @@ Heatmap Report
+ + + + + + > + + + + +
diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap_pyfile_template.html b/Lib/profiling/sampling/_heatmap_assets/heatmap_pyfile_template.html index 3fb6d3a6b91dbb..91b629b2628244 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap_pyfile_template.html +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap_pyfile_template.html @@ -17,14 +17,35 @@
- + + + + + + + + + + + > + + + + +
diff --git a/Lib/profiling/sampling/heatmap_collector.py b/Lib/profiling/sampling/heatmap_collector.py index 45649ce2009bb6..5b4c89283be08c 100644 --- a/Lib/profiling/sampling/heatmap_collector.py +++ b/Lib/profiling/sampling/heatmap_collector.py @@ -858,6 +858,7 @@ def _generate_index_html(self, index_path: Path, file_stats: List[FileStats]): "": f"", "": f"", "": self._template_loader.logo_html, + "": f"{sys.version_info.major}.{sys.version_info.minor}", "": str(len(file_stats)), "": f"{self._total_samples:,}", "": f"{self.stats.get('duration_sec', 0):.1f}s", @@ -915,6 +916,7 @@ def _generate_file_html(self, output_path: Path, filename: str, "": f"", "": f"", "": self._template_loader.logo_html, + "": f"{sys.version_info.major}.{sys.version_info.minor}", } html_content = self._template_loader.file_template diff --git a/Lib/profiling/sampling/stack_collector.py b/Lib/profiling/sampling/stack_collector.py index b7aa7f5ff82da3..e437facd8bb94b 100644 --- a/Lib/profiling/sampling/stack_collector.py +++ b/Lib/profiling/sampling/stack_collector.py @@ -5,6 +5,7 @@ import json import linecache import os +import sys from ._css_utils import get_combined_css from .collector import Collector, extract_lineno @@ -393,6 +394,9 @@ def _create_flamegraph_html(self, data): # Let CSS control size; keep markup simple logo_html = f'Tachyon logo' html_template = html_template.replace("", logo_html) + html_template = html_template.replace( + "", f"{sys.version_info.major}.{sys.version_info.minor}" + ) d3_js = d3_path.read_text(encoding="utf-8") fg_css = fg_css_path.read_text(encoding="utf-8") From 15313dd3d74490f570a3c361a4176437a8320af6 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 12 Dec 2025 17:48:43 +0100 Subject: [PATCH 6/7] gh-140550: Correct error message for PyModExport (PEP 793) hook (GH-142583) --- Python/import.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/import.c b/Python/import.c index 4dd247fac27654..2860ae032dfe29 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2003,7 +2003,7 @@ import_run_modexport(PyThreadState *tstate, PyModExportFunction ex0, if (!PyErr_Occurred()) { PyErr_Format( PyExc_SystemError, - "slot export function for module %s failed without setting an exception", + "module export hook for module %R failed without setting an exception", info->name); } return NULL; @@ -2011,7 +2011,7 @@ import_run_modexport(PyThreadState *tstate, PyModExportFunction ex0, if (PyErr_Occurred()) { PyErr_Format( PyExc_SystemError, - "slot export function for module %s raised unreported exception", + "module export hook for module %R raised unreported exception", info->name); } PyObject *result = PyModule_FromSlotsAndSpec(slots, spec); From 6d644e4453a907710e11f6f6b6b8890c197370d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Preng=C3=A8re?= <2138730+alexprengere@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:58:12 +0100 Subject: [PATCH 7/7] gh-141939: Add colors to interpolated values in argparse (#141940) Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Savannah Ostrowski --- Lib/_colorize.py | 2 +- Lib/argparse.py | 8 ++++-- Lib/test/test_argparse.py | 28 ++++++++++++------- ...-11-28-08-25-19.gh-issue-141939.BXPnFj.rst | 1 + 4 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-11-28-08-25-19.gh-issue-141939.BXPnFj.rst diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 29d7cc67b6e39d..0b7047620b4556 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -169,7 +169,7 @@ class Argparse(ThemeSection): label: str = ANSIColors.BOLD_YELLOW action: str = ANSIColors.BOLD_GREEN default: str = ANSIColors.GREY - default_value: str = ANSIColors.YELLOW + interpolated_value: str = ANSIColors.YELLOW reset: str = ANSIColors.RESET error: str = ANSIColors.BOLD_MAGENTA warning: str = ANSIColors.BOLD_YELLOW diff --git a/Lib/argparse.py b/Lib/argparse.py index ed98aa9e974b2a..ee7ebc4696a0f7 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -668,6 +668,10 @@ def _expand_help(self, action): params[name] = value.__name__ if params.get('choices') is not None: params['choices'] = ', '.join(map(str, params['choices'])) + # Before interpolating, wrap the values with color codes + t = self._theme + for name, value in params.items(): + params[name] = f"{t.interpolated_value}{value}{t.reset}" return help_string % params def _iter_indented_subactions(self, action): @@ -749,8 +753,8 @@ def _get_help_string(self, action): default_str = _(" (default: %(default)s)") prefix, suffix = default_str.split("%(default)s") help += ( - f" {t.default}{prefix.lstrip()}" - f"{t.default_value}%(default)s" + f" {t.default}{prefix.lstrip()}{t.reset}" + f"%(default)s" f"{t.default}{suffix}{t.reset}" ) return help diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 24e8ab1c5cacbb..0f93e8ea740770 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -7308,6 +7308,13 @@ def test_argparse_color(self): choices=("Aaaaa", "Bbbbb", "Ccccc", "Ddddd"), help="pick one", ) + parser.add_argument( + "--optional8", + default="A", + metavar="X", + choices=("A", "B", "C"), + help="among %(choices)s, default is %(default)s", + ) parser.add_argument("+f") parser.add_argument("++bar") @@ -7334,7 +7341,7 @@ def test_argparse_color(self): label_b = self.theme.label pos_b = self.theme.action default = self.theme.default - default_value = self.theme.default_value + interp = self.theme.interpolated_value reset = self.theme.reset # Act @@ -7347,8 +7354,8 @@ def test_argparse_color(self): f"""\ {heading}usage: {reset}{prog}PROG{reset} [{short}-h{reset}] [{short}-v{reset} | {short}-q{reset}] [{short}-o{reset}] [{long}--optional2 {label}OPTIONAL2{reset}] [{long}--optional3 {label}{{X,Y,Z}}{reset}] [{long}--optional4 {label}{{X,Y,Z}}{reset}] [{long}--optional5 {label}{{X,Y,Z}}{reset}] [{long}--optional6 {label}{{X,Y,Z}}{reset}] - [{short}-p {label}{{Aaaaa,Bbbbb,Ccccc,Ddddd}}{reset}] [{short}+f {label}F{reset}] [{long}++bar {label}BAR{reset}] [{long}-+baz {label}BAZ{reset}] - [{short}-c {label}COUNT{reset}] + [{short}-p {label}{{Aaaaa,Bbbbb,Ccccc,Ddddd}}{reset}] [{long}--optional8 {label}X{reset}] [{short}+f {label}F{reset}] [{long}++bar {label}BAR{reset}] + [{long}-+baz {label}BAZ{reset}] [{short}-c {label}COUNT{reset}] {pos}x{reset} {pos}y{reset} {pos}this_indeed_is_a_very_long_action_name{reset} {pos}{{sub1,sub2}} ...{reset} Colorful help @@ -7361,17 +7368,18 @@ def test_argparse_color(self): {heading}options:{reset} {short_b}-h{reset}, {long_b}--help{reset} show this help message and exit - {short_b}-v{reset}, {long_b}--verbose{reset} more spam {default}(default: {default_value}False{default}){reset} - {short_b}-q{reset}, {long_b}--quiet{reset} less spam {default}(default: {default_value}False{default}){reset} + {short_b}-v{reset}, {long_b}--verbose{reset} more spam {default}(default: {reset}{interp}False{reset}{default}){reset} + {short_b}-q{reset}, {long_b}--quiet{reset} less spam {default}(default: {reset}{interp}False{reset}{default}){reset} {short_b}-o{reset}, {long_b}--optional1{reset} {long_b}--optional2{reset} {label_b}OPTIONAL2{reset} - pick one {default}(default: {default_value}None{default}){reset} + pick one {default}(default: {reset}{interp}None{reset}{default}){reset} {long_b}--optional3{reset} {label_b}{{X,Y,Z}}{reset} - {long_b}--optional4{reset} {label_b}{{X,Y,Z}}{reset} pick one {default}(default: {default_value}None{default}){reset} - {long_b}--optional5{reset} {label_b}{{X,Y,Z}}{reset} pick one {default}(default: {default_value}None{default}){reset} - {long_b}--optional6{reset} {label_b}{{X,Y,Z}}{reset} pick one {default}(default: {default_value}None{default}){reset} + {long_b}--optional4{reset} {label_b}{{X,Y,Z}}{reset} pick one {default}(default: {reset}{interp}None{reset}{default}){reset} + {long_b}--optional5{reset} {label_b}{{X,Y,Z}}{reset} pick one {default}(default: {reset}{interp}None{reset}{default}){reset} + {long_b}--optional6{reset} {label_b}{{X,Y,Z}}{reset} pick one {default}(default: {reset}{interp}None{reset}{default}){reset} {short_b}-p{reset}, {long_b}--optional7{reset} {label_b}{{Aaaaa,Bbbbb,Ccccc,Ddddd}}{reset} - pick one {default}(default: {default_value}None{default}){reset} + pick one {default}(default: {reset}{interp}None{reset}{default}){reset} + {long_b}--optional8{reset} {label_b}X{reset} among {interp}A, B, C{reset}, default is {interp}A{reset} {short_b}+f{reset} {label_b}F{reset} {long_b}++bar{reset} {label_b}BAR{reset} {long_b}-+baz{reset} {label_b}BAZ{reset} diff --git a/Misc/NEWS.d/next/Library/2025-11-28-08-25-19.gh-issue-141939.BXPnFj.rst b/Misc/NEWS.d/next/Library/2025-11-28-08-25-19.gh-issue-141939.BXPnFj.rst new file mode 100644 index 00000000000000..1015d90c501fd0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-11-28-08-25-19.gh-issue-141939.BXPnFj.rst @@ -0,0 +1 @@ +Add color to all interpolated values in :mod:`argparse` help, like ``%(default)s`` or ``%(choices)s``. Patch by Alex Prengère.