]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-139640: Fix swallowing syntax warnings in different modules (GH-139755)
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 14 Oct 2025 14:48:09 +0000 (17:48 +0300)
committerGitHub <noreply@github.com>
Tue, 14 Oct 2025 14:48:09 +0000 (17:48 +0300)
Revert GH-131993.

Fix swallowing some syntax warnings in different modules if they accidentally
have the same message and are emitted from the same line.

Include/cpython/warnings.h
Lib/test/test_compile.py
Lib/test/test_pyrepl/test_interact.py
Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb.rst [new file with mode: 0644]
Python/_warnings.c
Python/compile.c
Python/errors.c

index 8731fd2e96b716fd87d988d40320e0ab4b15651f..4e3eb88e8ff4472b37f1903184097757271d6c5b 100644 (file)
@@ -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);
index 1660dabe6815aad282604121ad2b85567da1aeaa..bc8ef93cb8f9de9038110ebd377f9327d92dbfd2 100644 (file)
@@ -1679,22 +1679,21 @@ class TestSpecifics(unittest.TestCase):
         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', '<stdin>', 'eval')
-            compile('1 is 1', '<stdin>', 'eval')
-
-        self.assertEqual(len(caught), 1)
+            for i in range(2):
+                # Even if compile() is at the same line.
+                compile(source, '<stdin>', 'exec')
 
-        with warnings.catch_warnings(record=True) as caught:
-            warnings.simplefilter("always")
-            compile('1 is 1', '<stdin>', 'eval')
-            compile('1 is 1', '<stdin>', '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
@@ -1705,16 +1704,47 @@ class TestSpecifics(unittest.TestCase):
             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, '<stdin>', '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, '<stdin>', '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:
index 8c0eeab6dcae96bfde4aad47957921df15e18dcc..1a3146da8eadc80de02c3cf1bb095c88bc2487a6 100644 (file)
@@ -1,7 +1,6 @@
 import contextlib
 import io
 import unittest
-import warnings
 from unittest.mock import patch
 from textwrap import dedent
 
@@ -274,28 +273,3 @@ class TestMoreLines(unittest.TestCase):
         code = "if foo:"
         console = InteractiveColoredConsole(namespace, filename="<stdin>")
         self.assertTrue(_more_lines(console, code))
-
-
-class TestWarnings(unittest.TestCase):
-    def test_pep_765_warning(self):
-        """
-        Test that a SyntaxWarning emitted from the
-        AST optimizer is only shown once in the REPL.
-        """
-        # gh-131927
-        console = InteractiveColoredConsole()
-        code = dedent("""\
-        def f():
-            try:
-                return 1
-            finally:
-                return 2
-        """)
-
-        with warnings.catch_warnings(record=True) as caught:
-            warnings.simplefilter("default")
-            console.runsource(code)
-
-        count = sum("'return' in a 'finally' block" in str(w.message)
-                    for w in caught)
-        self.assertEqual(count, 1)
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 (file)
index 0000000..f1344ae
--- /dev/null
@@ -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.
index 243a5e88e9dbbc141d25887394fce3125eab0757..9989b623dbce3a29b3b2bb8498a168c3fa7f5470 100644 (file)
@@ -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, &registry)) {
-        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,
index c04391e682f9ac90f06f0898fd99d3e28285580c..8070d3f03760efcd4f3c1fc349a84220b9435df4 100644 (file)
@@ -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);
index 2688396004e98bd32a46c6f97df34848f1407713..9fe95cec0ab7945a5ed1e8ebdd7126d5be72cd9b 100644 (file)
@@ -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