]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-131927: Do not emit PEP 765 warnings in ast.parse() (GH-139642) (GH-140786)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 30 Oct 2025 11:25:34 +0000 (12:25 +0100)
committerGitHub <noreply@github.com>
Thu, 30 Oct 2025 11:25:34 +0000 (11:25 +0000)
ast.parse() no longer emits syntax warnings for
return/break/continue in finally (see PEP-765) -- they are only
emitted during compilation.
(cherry picked from commit ad0a3f733b23e7fc69aff13055c7fac8ab9dcd66)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Include/internal/pycore_compile.h
Lib/test/test_ast/test_ast.py
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.gY5oTb2.rst [new file with mode: 0644]
Python/ast_preprocess.c
Python/compile.c

index aecc50be1e6c34a88d14fd5b1858bceaae2e0baf..ed776bbb73a0ee9b2b1d7a46e2c0ff2945f0a4a6 100644 (file)
@@ -49,7 +49,8 @@ extern int _PyAST_Preprocess(
     PyObject *filename,
     int optimize,
     int ff_features,
-    int syntax_check_only);
+    int syntax_check_only,
+    int enable_warnings);
 
 
 typedef struct {
index 963a3e705e2b8167ce30a87150a048ab99a0c3e2..361ee508376d2a39734fdb16e4157cec0c169510 100644 (file)
@@ -1057,61 +1057,6 @@ class AST_Tests(unittest.TestCase):
                                     r"Exceeds the limit \(\d+ digits\)"):
             repr(ast.Constant(value=eval(source)))
 
-    def test_pep_765_warnings(self):
-        srcs = [
-            textwrap.dedent("""
-                 def f():
-                     try:
-                         pass
-                     finally:
-                         return 42
-                 """),
-            textwrap.dedent("""
-                 for x in y:
-                     try:
-                         pass
-                     finally:
-                         break
-                 """),
-            textwrap.dedent("""
-                 for x in y:
-                     try:
-                         pass
-                     finally:
-                         continue
-                 """),
-        ]
-        for src in srcs:
-            with self.assertWarnsRegex(SyntaxWarning, 'finally'):
-                ast.parse(src)
-
-    def test_pep_765_no_warnings(self):
-        srcs = [
-            textwrap.dedent("""
-                 try:
-                     pass
-                 finally:
-                     def f():
-                         return 42
-                 """),
-            textwrap.dedent("""
-                 try:
-                     pass
-                 finally:
-                     for x in y:
-                         break
-                 """),
-            textwrap.dedent("""
-                 try:
-                     pass
-                 finally:
-                     for x in y:
-                         continue
-                 """),
-        ]
-        for src in srcs:
-            ast.parse(src)
-
     def test_tstring(self):
         # Test AST structure for simple t-string
         tree = ast.parse('t"Hello"')
index b2beba6e70e2ae53da95bd2ce0ed465214318d0a..b978be8bc6862b2fea7b7c652d122a9e0763174d 100644 (file)
@@ -1728,6 +1728,66 @@ class TestSpecifics(unittest.TestCase):
             self.assertEqual(wm.category, SyntaxWarning)
             self.assertIn("\"is\" with 'int' literal", str(wm.message))
 
+    @support.subTests('src', [
+        textwrap.dedent("""
+            def f():
+                try:
+                    pass
+                finally:
+                    return 42
+            """),
+        textwrap.dedent("""
+            for x in y:
+                try:
+                    pass
+                finally:
+                    break
+            """),
+        textwrap.dedent("""
+            for x in y:
+                try:
+                    pass
+                finally:
+                    continue
+            """),
+    ])
+    def test_pep_765_warnings(self, src):
+        with self.assertWarnsRegex(SyntaxWarning, 'finally'):
+            compile(src, '<string>', 'exec')
+        with warnings.catch_warnings():
+            warnings.simplefilter("error")
+            tree = ast.parse(src)
+        with self.assertWarnsRegex(SyntaxWarning, 'finally'):
+            compile(tree, '<string>', 'exec')
+
+    @support.subTests('src', [
+        textwrap.dedent("""
+            try:
+                pass
+            finally:
+                def f():
+                    return 42
+            """),
+        textwrap.dedent("""
+            try:
+                pass
+            finally:
+                for x in y:
+                    break
+            """),
+        textwrap.dedent("""
+            try:
+                pass
+            finally:
+                for x in y:
+                    continue
+            """),
+    ])
+    def test_pep_765_no_warnings(self, src):
+        with warnings.catch_warnings():
+            warnings.simplefilter("error")
+            compile(src, '<string>', 'exec')
+
 
 class TestBooleanExpression(unittest.TestCase):
     class Value:
index af5d4d0e67632a19ad8dec0387027ea448b5dcb2..f0837ee94e9beb436d8d880f621468a236b9f404 100644 (file)
@@ -1,5 +1,6 @@
 import contextlib
 import io
+import warnings
 import unittest
 from unittest.mock import patch
 from textwrap import dedent
@@ -273,3 +274,28 @@ 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("always")
+            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.gY5oTb2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb2.rst
new file mode 100644 (file)
index 0000000..b147b43
--- /dev/null
@@ -0,0 +1,3 @@
+:func:`ast.parse` no longer emits syntax warnings for
+``return``/``break``/``continue`` in ``finally`` (see :pep:`765`) -- they are
+only emitted during compilation.
index 44d3075098be75252d6d61bee046ab6e668c09f3..fe6fd9479d1531f97868b004677a6acd7173cc6b 100644 (file)
@@ -19,6 +19,7 @@ typedef struct {
     int optimize;
     int ff_features;
     int syntax_check_only;
+    int enable_warnings;
 
     _Py_c_array_t cf_finally;       /* context for PEP 765 check */
     int cf_finally_used;
@@ -78,7 +79,7 @@ control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTPreprocessState
 static int
 before_return(_PyASTPreprocessState *state, stmt_ty node_)
 {
-    if (state->cf_finally_used > 0) {
+    if (state->enable_warnings && state->cf_finally_used > 0) {
         ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
         if (ctx->in_finally && ! ctx->in_funcdef) {
             if (!control_flow_in_finally_warning("return", node_, state)) {
@@ -92,7 +93,7 @@ before_return(_PyASTPreprocessState *state, stmt_ty node_)
 static int
 before_loop_exit(_PyASTPreprocessState *state, stmt_ty node_, const char *kw)
 {
-    if (state->cf_finally_used > 0) {
+    if (state->enable_warnings && state->cf_finally_used > 0) {
         ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
         if (ctx->in_finally && ! ctx->in_loop) {
             if (!control_flow_in_finally_warning(kw, node_, state)) {
@@ -968,7 +969,7 @@ astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTPreprocessState *st
 
 int
 _PyAST_Preprocess(mod_ty mod, PyArena *arena, PyObject *filename, int optimize,
-                  int ff_features, int syntax_check_only)
+                  int ff_features, int syntax_check_only, int enable_warnings)
 {
     _PyASTPreprocessState state;
     memset(&state, 0, sizeof(_PyASTPreprocessState));
@@ -976,6 +977,7 @@ _PyAST_Preprocess(mod_ty mod, PyArena *arena, PyObject *filename, int optimize,
     state.optimize = optimize;
     state.ff_features = ff_features;
     state.syntax_check_only = syntax_check_only;
+    state.enable_warnings = enable_warnings;
     if (_Py_CArray_Init(&state.cf_finally, sizeof(ControlFlowInFinallyContext), 20) < 0) {
         return -1;
     }
index 8070d3f03760efcd4f3c1fc349a84220b9435df4..e2f1c7e8eb5bce14d8c0dec9f175c773bcd0623b 100644 (file)
@@ -136,7 +136,7 @@ compiler_setup(compiler *c, mod_ty mod, PyObject *filename,
     c->c_optimize = (optimize == -1) ? _Py_GetConfig()->optimization_level : optimize;
     c->c_save_nested_seqs = false;
 
-    if (!_PyAST_Preprocess(mod, arena, filename, c->c_optimize, merged, 0)) {
+    if (!_PyAST_Preprocess(mod, arena, filename, c->c_optimize, merged, 0, 1)) {
         return ERROR;
     }
     c->c_st = _PySymtable_Build(mod, filename, &c->c_future);
@@ -1502,7 +1502,7 @@ _PyCompile_AstPreprocess(mod_ty mod, PyObject *filename, PyCompilerFlags *cf,
     if (optimize == -1) {
         optimize = _Py_GetConfig()->optimization_level;
     }
-    if (!_PyAST_Preprocess(mod, arena, filename, optimize, flags, no_const_folding)) {
+    if (!_PyAST_Preprocess(mod, arena, filename, optimize, flags, no_const_folding, 0)) {
         return -1;
     }
     return 0;