From 3990961b08c0ed4ff22574f16f62d98aa7c9a0f6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 6 Oct 2025 10:03:41 +0300 Subject: [PATCH] gh-139640: Fix swallowing syntax warnings in different modules Revert GH-131993. Explicitly silence the duplicated PEP 765 syntax warnings in the REPL. Fix swallowing some syntax warnings in different modules if they accidentally have the same message and are emitted from the same line. Co-authored-by: Tomas R. --- Include/cpython/warnings.h | 6 -- Lib/_pyrepl/console.py | 20 +++++- Lib/test/test_compile.py | 66 ++++++++++++++----- Lib/test/test_pyrepl/test_interact.py | 2 +- ...-10-06-10-03-37.gh-issue-139640.gY5oTb.rst | 2 + Python/_warnings.c | 22 ------- Python/compile.c | 10 +++ Python/errors.c | 4 +- 8 files changed, 82 insertions(+), 50 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb.rst diff --git a/Include/cpython/warnings.h b/Include/cpython/warnings.h index 8731fd2e96b716..4e3eb88e8ff447 100644 --- a/Include/cpython/warnings.h +++ b/Include/cpython/warnings.h @@ -18,9 +18,3 @@ PyAPI_FUNC(int) PyErr_WarnExplicitFormat( // DEPRECATED: Use PyErr_WarnEx() instead. #define PyErr_Warn(category, msg) PyErr_WarnEx((category), (msg), 1) - -int _PyErr_WarnExplicitObjectWithContext( - PyObject *category, - PyObject *message, - PyObject *filename, - int lineno); diff --git a/Lib/_pyrepl/console.py b/Lib/_pyrepl/console.py index e0535d50396316..a21b2f22d818e8 100644 --- a/Lib/_pyrepl/console.py +++ b/Lib/_pyrepl/console.py @@ -217,8 +217,26 @@ def runsource(self, source, filename="", symbol="single"): wrapper = ast.Interactive if stmt is last_stmt else ast.Module the_symbol = symbol if stmt is last_stmt else "exec" item = wrapper([stmt]) + import warnings try: - code = self.compile.compiler(item, filename, the_symbol) + with warnings.catch_warnings(record=True) as caught_warnings: + # Enable all warnings + warnings.simplefilter("always") + code = self.compile.compiler(item, filename, the_symbol) + for warning in caught_warnings: + if issubclass(warning.category, SyntaxWarning) and "in a 'finally' block" in str(warning.message): + # Ignore this warning as it would've alread been raised + # when compiling the code above + pass + else: + # Re-emit other warnings + warnings.warn_explicit( + message=warning.message, + category=warning.category, + filename=warning.filename, + lineno=warning.lineno, + source=warning.source + ) linecache._register_code(code, source, filename) except SyntaxError as e: if e.args[0] == "'await' outside function": diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 277a2a187543d4..2d1fc43008411f 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1664,22 +1664,21 @@ class WeirdDict(dict): self.assertRaises(NameError, ns['foo']) def test_compile_warnings(self): - # See gh-131927 - # Compile warnings originating from the same file and - # line are now only emitted once. + # Each invocation of compile() emits compiler warnings, even if they + # have the same message and line number. + source = textwrap.dedent(r""" + # tokenizer + 1or 0 # line 3 + # code generator + 1 is 1 # line 5 + """) with warnings.catch_warnings(record=True) as caught: warnings.simplefilter("default") - compile('1 is 1', '', 'eval') - compile('1 is 1', '', 'eval') - - self.assertEqual(len(caught), 1) + for i in range(2): + # Even if compile() is at the same line. + compile(source, '', 'exec') - with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter("always") - compile('1 is 1', '', 'eval') - compile('1 is 1', '', 'eval') - - self.assertEqual(len(caught), 2) + self.assertEqual([wm.lineno for wm in caught], [3, 5] * 2) def test_compile_warning_in_finally(self): # Ensure that warnings inside finally blocks are @@ -1690,16 +1689,47 @@ def test_compile_warning_in_finally(self): try: pass finally: - 1 is 1 + 1 is 1 # line 5 + try: + pass + finally: # nested + 1 is 1 # line 9 """) with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter("default") + warnings.simplefilter("always") compile(source, '', 'exec') - self.assertEqual(len(caught), 1) - self.assertEqual(caught[0].category, SyntaxWarning) - self.assertIn("\"is\" with 'int' literal", str(caught[0].message)) + self.assertEqual(sorted(wm.lineno for wm in caught), [5, 9]) + for wm in caught: + self.assertEqual(wm.category, SyntaxWarning) + self.assertIn("\"is\" with 'int' literal", str(wm.message)) + + # Other code path is used for "try" with "except*". + source = textwrap.dedent(""" + try: + pass + except *Exception: + pass + finally: + 1 is 1 # line 7 + try: + pass + except *Exception: + pass + finally: # nested + 1 is 1 # line 13 + """) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + compile(source, '', 'exec') + + self.assertEqual(sorted(wm.lineno for wm in caught), [7, 13]) + for wm in caught: + self.assertEqual(wm.category, SyntaxWarning) + self.assertIn("\"is\" with 'int' literal", str(wm.message)) + class TestBooleanExpression(unittest.TestCase): class Value: diff --git a/Lib/test/test_pyrepl/test_interact.py b/Lib/test/test_pyrepl/test_interact.py index 8c0eeab6dcae96..d70fa4ce2ff09f 100644 --- a/Lib/test/test_pyrepl/test_interact.py +++ b/Lib/test/test_pyrepl/test_interact.py @@ -293,7 +293,7 @@ def f(): """) with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter("default") + warnings.simplefilter("always") console.runsource(code) count = sum("'return' in a 'finally' block" in str(w.message) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb.rst new file mode 100644 index 00000000000000..f1344aedb58286 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb.rst @@ -0,0 +1,2 @@ +Fix swallowing some syntax warnings in different modules if they +accidentally have the same message and are emitted from the same line. diff --git a/Python/_warnings.c b/Python/_warnings.c index 243a5e88e9dbbc..9989b623dbce3a 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -1473,28 +1473,6 @@ PyErr_WarnExplicitObject(PyObject *category, PyObject *message, return 0; } -/* Like PyErr_WarnExplicitObject, but automatically sets up context */ -int -_PyErr_WarnExplicitObjectWithContext(PyObject *category, PyObject *message, - PyObject *filename, int lineno) -{ - PyObject *unused_filename, *module, *registry; - int unused_lineno; - int stack_level = 1; - - if (!setup_context(stack_level, NULL, &unused_filename, &unused_lineno, - &module, ®istry)) { - return -1; - } - - int rc = PyErr_WarnExplicitObject(category, message, filename, lineno, - module, registry); - Py_DECREF(unused_filename); - Py_DECREF(registry); - Py_DECREF(module); - return rc; -} - int PyErr_WarnExplicit(PyObject *category, const char *text, const char *filename_str, int lineno, diff --git a/Python/compile.c b/Python/compile.c index c04391e682f9ac..8070d3f03760ef 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -103,6 +103,7 @@ typedef struct _PyCompiler { bool c_save_nested_seqs; /* if true, construct recursive instruction sequences * (including instructions for nested code objects) */ + int c_disable_warning; } compiler; static int @@ -765,6 +766,9 @@ _PyCompile_PushFBlock(compiler *c, location loc, f->fb_loc = loc; f->fb_exit = exit; f->fb_datum = datum; + if (t == COMPILE_FBLOCK_FINALLY_END) { + c->c_disable_warning++; + } return SUCCESS; } @@ -776,6 +780,9 @@ _PyCompile_PopFBlock(compiler *c, fblocktype t, jump_target_label block_label) u->u_nfblocks--; assert(u->u_fblock[u->u_nfblocks].fb_type == t); assert(SAME_JUMP_TARGET_LABEL(u->u_fblock[u->u_nfblocks].fb_block, block_label)); + if (t == COMPILE_FBLOCK_FINALLY_END) { + c->c_disable_warning--; + } } fblockinfo * @@ -1203,6 +1210,9 @@ _PyCompile_Error(compiler *c, location loc, const char *format, ...) int _PyCompile_Warn(compiler *c, location loc, const char *format, ...) { + if (c->c_disable_warning) { + return 0; + } va_list vargs; va_start(vargs, format); PyObject *msg = PyUnicode_FromFormatV(format, vargs); diff --git a/Python/errors.c b/Python/errors.c index 2688396004e98b..9fe95cec0ab794 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1962,8 +1962,8 @@ int _PyErr_EmitSyntaxWarning(PyObject *msg, PyObject *filename, int lineno, int col_offset, int end_lineno, int end_col_offset) { - if (_PyErr_WarnExplicitObjectWithContext(PyExc_SyntaxWarning, msg, - filename, lineno) < 0) + if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg, + filename, lineno, NULL, NULL) < 0) { if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) { /* Replace the SyntaxWarning exception with a SyntaxError