https://github.com/python/cpython/commit/ad0a3f733b23e7fc69aff13055c7fac8ab9dcd66
commit: ad0a3f733b23e7fc69aff13055c7fac8ab9dcd66
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-10-30T13:00:42+02:00
summary:
gh-131927: Do not emit PEP 765 warnings in ast.parse() (GH-139642)
ast.parse() no longer emits syntax warnings for
return/break/continue in finally (see PEP-765) -- they are only
emitted during compilation.
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb2.rst
M Include/internal/pycore_compile.h
M Lib/test/test_ast/test_ast.py
M Lib/test/test_compile.py
M Lib/test/test_pyrepl/test_interact.py
M Python/ast_preprocess.c
M Python/compile.c
diff --git a/Include/internal/pycore_compile.h
b/Include/internal/pycore_compile.h
index c18e04bf67a5df..1c60834fa2058c 100644
--- a/Include/internal/pycore_compile.h
+++ b/Include/internal/pycore_compile.h
@@ -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 {
diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py
index 5fdb3a458ae999..a979a4b1da1ad1 100644
--- a/Lib/test/test_ast/test_ast.py
+++ b/Lib/test/test_ast/test_ast.py
@@ -1057,61 +1057,6 @@ def test_repr_large_input_crash(self):
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"')
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index bc8ef93cb8f9de..846d38ae561fc5 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -1745,6 +1745,66 @@ def test_compile_warning_in_finally(self):
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:
diff --git a/Lib/test/test_pyrepl/test_interact.py
b/Lib/test/test_pyrepl/test_interact.py
index 1a3146da8eadc8..fd4530ebc004aa 100644
--- a/Lib/test/test_pyrepl/test_interact.py
+++ b/Lib/test/test_pyrepl/test_interact.py
@@ -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 @@ def test_incomplete_statement(self):
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
index 00000000000000..b147b430ccccf5
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb2.rst
@@ -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.
diff --git a/Python/ast_preprocess.c b/Python/ast_preprocess.c
index 44d3075098be75..fe6fd9479d1531 100644
--- a/Python/ast_preprocess.c
+++ b/Python/ast_preprocess.c
@@ -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;
}
diff --git a/Python/compile.c b/Python/compile.c
index 8070d3f03760ef..e2f1c7e8eb5bce 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -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;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]