]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-139640: Fix swallowing syntax warnings in different modules (GH-139755...
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 14 Oct 2025 15:46:11 +0000 (18:46 +0300)
committerGitHub <noreply@github.com>
Tue, 14 Oct 2025 15:46:11 +0000 (15:46 +0000)
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.

Fix duplicated warnings in the "finally" block.

(cherry picked from commit 279db6bede30be3a1b86803585eb4404d27800da)

Include/cpython/warnings.h
Lib/test/test_compile.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

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 eda2c0c40494ad6192797bf88f4d5c8944306d77..06b5fd5bbc8318498cd4bcba818d504b23f04f92 100644 (file)
@@ -1528,22 +1528,21 @@ class TestSpecifics(unittest.TestCase):
             [[]]
 
     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
@@ -1554,16 +1553,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))
+
 
 @requires_debug_ranges()
 class TestSourcePositions(unittest.TestCase):
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..396e40f
--- /dev/null
@@ -0,0 +1,3 @@
+Fix swallowing some syntax warnings in different modules if they
+accidentally have the same message and are emitted from the same line.
+Fix duplicated warnings in the ``finally`` block.
index 5bbd4a9c19f6c99329da09a40e5db72caf55693a..4bb83b214ae6cc75bffd60a6185da40e38deccdf 100644 (file)
@@ -1317,28 +1317,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 bb2c2293a38c9a3fe291c96b32062f201f64180e..8f8b6773440d854a575c89d41835ee82e93e326c 100644 (file)
@@ -291,6 +291,7 @@ struct compiler {
     bool c_save_nested_seqs;     /* if true, construct recursive instruction sequences
                                   * (including instructions for nested code objects)
                                   */
+    int c_disable_warning;
 };
 
 #define INSTR_SEQUENCE(C) ((C)->u->u_instr_sequence)
@@ -1437,6 +1438,9 @@ compiler_push_fblock(struct compiler *c, location loc,
     f->fb_loc = loc;
     f->fb_exit = exit;
     f->fb_datum = datum;
+    if (t == FINALLY_END) {
+        c->c_disable_warning++;
+    }
     return SUCCESS;
 }
 
@@ -1448,6 +1452,9 @@ compiler_pop_fblock(struct compiler *c, enum fblocktype t, jump_target_label blo
     u->u_nfblocks--;
     assert(u->u_fblock[u->u_nfblocks].fb_type == t);
     assert(SAME_LABEL(u->u_fblock[u->u_nfblocks].fb_block, block_label));
+    if (t == FINALLY_END) {
+        c->c_disable_warning--;
+    }
 }
 
 static int
@@ -6609,6 +6616,9 @@ static int
 compiler_warn(struct compiler *c, location loc,
               const char *format, ...)
 {
+    if (c->c_disable_warning) {
+        return SUCCESS;
+    }
     va_list vargs;
     va_start(vargs, format);
     PyObject *msg = PyUnicode_FromFormatV(format, vargs);
@@ -6616,8 +6626,8 @@ compiler_warn(struct compiler *c, location loc,
     if (msg == NULL) {
         return ERROR;
     }
-    if (_PyErr_WarnExplicitObjectWithContext(PyExc_SyntaxWarning, msg,
-                                             c->c_filename, loc.lineno) < 0)
+    if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg,
+                                 c->c_filename, loc.lineno, NULL, NULL) < 0)
     {
         if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
             /* Replace the SyntaxWarning exception with a SyntaxError