https://github.com/python/cpython/commit/ffc2f1dd1c023b44b488311511db790a96d757db
commit: ffc2f1dd1c023b44b488311511db790a96d757db
branch: main
author: Irit Katriel <1055913+iritkatr...@users.noreply.github.com>
committer: iritkatriel <1055913+iritkatr...@users.noreply.github.com>
date: 2025-03-17T20:48:54Z
summary:

gh-130080: implement PEP 765 (#130087)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-19-07-54.gh-issue-130080.IoJpuy.rst
M Doc/reference/compound_stmts.rst
M Doc/tutorial/errors.rst
M Doc/whatsnew/3.14.rst
M Include/internal/pycore_compile.h
M Lib/test/test___all__.py
M Lib/test/test_ast/test_ast.py
M Lib/test/test_except_star.py
M Lib/test/test_syntax.py
M Lib/test/test_unparse.py
M Python/ast_opt.c
M Python/bltinmodule.c
M Python/compile.c
M Python/pythonrun.c

diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst
index 71cc0c83de567e..949cdf3be8b7e3 100644
--- a/Doc/reference/compound_stmts.rst
+++ b/Doc/reference/compound_stmts.rst
@@ -420,16 +420,16 @@ is executed.  If there is a saved exception it is 
re-raised at the end of the
 :keyword:`!finally` clause.  If the :keyword:`!finally` clause raises another
 exception, the saved exception is set as the context of the new exception.
 If the :keyword:`!finally` clause executes a :keyword:`return`, 
:keyword:`break`
-or :keyword:`continue` statement, the saved exception is discarded::
+or :keyword:`continue` statement, the saved exception is discarded. For 
example,
+this function returns 42.
 
-   >>> def f():
-   ...     try:
-   ...         1/0
-   ...     finally:
-   ...         return 42
-   ...
-   >>> f()
-   42
+.. code-block::
+
+   def f():
+       try:
+           1/0
+       finally:
+           return 42
 
 The exception information is not available to the program during execution of
 the :keyword:`!finally` clause.
@@ -446,21 +446,25 @@ statement, the :keyword:`!finally` clause is also 
executed 'on the way out.'
 The return value of a function is determined by the last :keyword:`return`
 statement executed.  Since the :keyword:`!finally` clause always executes, a
 :keyword:`!return` statement executed in the :keyword:`!finally` clause will
-always be the last one executed::
+always be the last one executed. The following function returns 'finally'.
 
-   >>> def foo():
-   ...     try:
-   ...         return 'try'
-   ...     finally:
-   ...         return 'finally'
-   ...
-   >>> foo()
-   'finally'
+.. code-block::
+
+   def foo():
+       try:
+           return 'try'
+       finally:
+           return 'finally'
 
 .. versionchanged:: 3.8
    Prior to Python 3.8, a :keyword:`continue` statement was illegal in the
    :keyword:`!finally` clause due to a problem with the implementation.
 
+.. versionchanged:: next
+   The compiler emits a :exc:`SyntaxWarning` when a :keyword:`return`,
+   :keyword:`break` or :keyword:`continue` appears in a :keyword:`!finally`
+   block (see :pep:`765`).
+
 
 .. _with:
 .. _as:
diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst
index bfb281c1b7d66a..1c20fa2f0b6ae5 100644
--- a/Doc/tutorial/errors.rst
+++ b/Doc/tutorial/errors.rst
@@ -418,7 +418,9 @@ points discuss more complex cases when an exception occurs:
 
 * If the :keyword:`!finally` clause executes a :keyword:`break`,
   :keyword:`continue` or :keyword:`return` statement, exceptions are not
-  re-raised.
+  re-raised. This can be confusing and is therefore discouraged. From
+  version 3.14 the compiler emits a :exc:`SyntaxWarning` for it
+  (see :pep:`765`).
 
 * If the :keyword:`!try` statement reaches a :keyword:`break`,
   :keyword:`continue` or :keyword:`return` statement, the
@@ -430,7 +432,9 @@ points discuss more complex cases when an exception occurs:
   statement, the returned value will be the one from the
   :keyword:`!finally` clause's :keyword:`!return` statement, not the
   value from the :keyword:`!try` clause's :keyword:`!return`
-  statement.
+  statement. This can be confusing and is therefore discouraged. From
+  version 3.14 the compiler emits a :exc:`SyntaxWarning` for it
+  (see :pep:`765`).
 
 For example::
 
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index b1337190636529..789156974cb0d1 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -68,6 +68,7 @@ Summary -- release highlights
 * :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>`
 * :ref:`PEP 761: Discontinuation of PGP signatures <whatsnew314-pep761>`
 * :ref:`A new type of interpreter  <whatsnew314-tail-call>`
+* :ref:`PEP 765: Disallow return/break/continue that exit a finally block 
<whatsnew314-pep765>`
 
 
 Incompatible changes
@@ -370,6 +371,15 @@ Other language changes
   The testbed can also be used to run the test suite of projects other than
   CPython itself. (Contributed by Russell Keith-Magee in :gh:`127592`.)
 
+.. _whatsnew314-pep765:
+
+PEP 765: Disallow return/break/continue that exit a finally block
+-----------------------------------------------------------------
+
+The compiler emits a :exc:`SyntaxWarning` when a :keyword:`return`, 
:keyword:`break` or
+:keyword:`continue` statements appears where it exits a :keyword:`finally` 
block.
+This change is specified in :pep:`765`.
+
 New modules
 ===========
 
diff --git a/Include/internal/pycore_compile.h 
b/Include/internal/pycore_compile.h
index 5b08bb6f8a7135..467374c705486e 100644
--- a/Include/internal/pycore_compile.h
+++ b/Include/internal/pycore_compile.h
@@ -40,13 +40,16 @@ extern int _PyCompile_AstOptimize(
     PyObject *filename,
     PyCompilerFlags *flags,
     int optimize,
-    struct _arena *arena);
+    struct _arena *arena,
+    int syntax_check_only);
 
 extern int _PyAST_Optimize(
     struct _mod *,
     struct _arena *arena,
+    PyObject *filename,
     int optimize,
-    int ff_features);
+    int ff_features,
+    int syntax_check_only);
 
 
 typedef struct {
diff --git a/Lib/test/test___all__.py b/Lib/test/test___all__.py
index e405056c8ffcb5..f35b1194308262 100644
--- a/Lib/test/test___all__.py
+++ b/Lib/test/test___all__.py
@@ -37,6 +37,7 @@ def check_all(self, modname):
             (".* (module|package)", DeprecationWarning),
             (".* (module|package)", PendingDeprecationWarning),
             ("", ResourceWarning),
+            ("", SyntaxWarning),
             quiet=True):
             try:
                 exec("import %s" % modname, names)
@@ -52,6 +53,7 @@ def check_all(self, modname):
             with warnings_helper.check_warnings(
                 ("", DeprecationWarning),
                 ("", ResourceWarning),
+                ("", SyntaxWarning),
                 quiet=True):
                 try:
                     exec("from %s import *" % modname, names)
diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py
index 6e1458facafc30..e63ddb7d1fecc4 100644
--- a/Lib/test/test_ast/test_ast.py
+++ b/Lib/test/test_ast/test_ast.py
@@ -820,6 +820,61 @@ 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)
+
 
 class CopyTests(unittest.TestCase):
     """Test copying and pickling AST nodes."""
diff --git a/Lib/test/test_except_star.py b/Lib/test/test_except_star.py
index 284907f61213f8..47006c6d3a0c36 100644
--- a/Lib/test/test_except_star.py
+++ b/Lib/test/test_except_star.py
@@ -84,7 +84,8 @@ def test_break_in_except_star(self):
                     if i == 2:
                         break
                 finally:
-                    return 0
+                    pass
+                return 0
             """)
 
 
@@ -117,7 +118,8 @@ def test_continue_in_except_star_block_invalid(self):
                     if i == 2:
                         continue
                 finally:
-                    return 0
+                    pass
+                return 0
             """)
 
     def test_return_in_except_star_block_invalid(self):
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index 707d4fc6df16ea..d2950cf48abb21 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -858,7 +858,7 @@
 SyntaxError: 'function call' is an illegal expression for augmented assignment
 
 
-Test continue in finally in weird combinations.
+Test control flow in finally
 
 continue in for loop under finally should be ok.
 
@@ -872,51 +872,63 @@
     >>> test()
     9
 
-continue in a finally should be ok.
+break in for loop under finally should be ok.
 
     >>> def test():
-    ...    for abc in range(10):
-    ...        try:
-    ...            pass
-    ...        finally:
-    ...            continue
-    ...    print(abc)
+    ...     try:
+    ...         pass
+    ...     finally:
+    ...         for abc in range(10):
+    ...             break
+    ...     print(abc)
     >>> test()
-    9
+    0
+
+return in function under finally should be ok.
 
     >>> def test():
-    ...    for abc in range(10):
-    ...        try:
-    ...            pass
-    ...        finally:
-    ...            try:
-    ...                continue
-    ...            except:
-    ...                pass
-    ...    print(abc)
+    ...     try:
+    ...         pass
+    ...     finally:
+    ...         def f():
+    ...             return 42
+    ...     print(f())
     >>> test()
-    9
+    42
+
+combine for loop and function def
+
+return in function under finally should be ok.
 
     >>> def test():
-    ...    for abc in range(10):
-    ...        try:
-    ...            pass
-    ...        finally:
-    ...            try:
-    ...                pass
-    ...            except:
-    ...                continue
-    ...    print(abc)
+    ...     try:
+    ...         pass
+    ...     finally:
+    ...         for i in range(10):
+    ...             def f():
+    ...                 return 42
+    ...     print(f())
     >>> test()
-    9
+    42
+
+    >>> def test():
+    ...     try:
+    ...         pass
+    ...     finally:
+    ...         def f():
+    ...             for i in range(10):
+    ...                 return 42
+    ...     print(f())
+    >>> test()
+    42
 
 A continue outside loop should not be allowed.
 
     >>> def foo():
     ...     try:
-    ...         pass
-    ...     finally:
     ...         continue
+    ...     finally:
+    ...         pass
     Traceback (most recent call last):
       ...
     SyntaxError: 'continue' not properly in loop
@@ -2393,7 +2405,88 @@ def f(x: *b)
 
 from test import support
 
-class SyntaxTestCase(unittest.TestCase):
+class SyntaxWarningTest(unittest.TestCase):
+    def check_warning(self, code, errtext, filename="<testcase>", mode="exec"):
+        """Check that compiling code raises SyntaxWarning with errtext.
+
+        errtest is a regular expression that must be present in the
+        text of the warning raised.
+        """
+        with self.assertWarnsRegex(SyntaxWarning, errtext):
+            compile(code, filename, mode)
+
+    def test_return_in_finally(self):
+        source = textwrap.dedent("""
+            def f():
+                try:
+                    pass
+                finally:
+                    return 42
+            """)
+        self.check_warning(source, "'return' in a 'finally' block")
+
+        source = textwrap.dedent("""
+            def f():
+                try:
+                    pass
+                finally:
+                    try:
+                        return 42
+                    except:
+                        pass
+            """)
+        self.check_warning(source, "'return' in a 'finally' block")
+
+        source = textwrap.dedent("""
+            def f():
+                try:
+                    pass
+                finally:
+                    try:
+                        pass
+                    except:
+                        return 42
+            """)
+        self.check_warning(source, "'return' in a 'finally' block")
+
+    def test_break_and_continue_in_finally(self):
+        for kw in ('break', 'continue'):
+
+            source = textwrap.dedent(f"""
+                for abc in range(10):
+                    try:
+                        pass
+                    finally:
+                        {kw}
+                """)
+            self.check_warning(source, f"'{kw}' in a 'finally' block")
+
+            source = textwrap.dedent(f"""
+                for abc in range(10):
+                    try:
+                        pass
+                    finally:
+                        try:
+                            {kw}
+                        except:
+                            pass
+                """)
+            self.check_warning(source, f"'{kw}' in a 'finally' block")
+
+            source = textwrap.dedent(f"""
+                for abc in range(10):
+                    try:
+                        pass
+                    finally:
+                        try:
+                            pass
+                        except:
+                            {kw}
+                """)
+            self.check_warning(source, f"'{kw}' in a 'finally' block")
+
+
+class SyntaxErrorTestCase(unittest.TestCase):
 
     def _check_error(self, code, errtext,
                      filename="<testcase>", mode="exec", subclass=None,
@@ -2401,7 +2494,7 @@ def _check_error(self, code, errtext,
         """Check that compiling code raises SyntaxError with errtext.
 
         errtest is a regular expression that must be present in the
-        test of the exception raised.  If subclass is specified it
+        text of the exception raised.  If subclass is specified it
         is the expected subclass of SyntaxError (e.g. IndentationError).
         """
         try:
diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py
index f45a651c7ccb5d..9efea1e037f447 100644
--- a/Lib/test/test_unparse.py
+++ b/Lib/test/test_unparse.py
@@ -422,9 +422,11 @@ def test_docstrings(self):
             self.check_ast_roundtrip(f"'''{docstring}'''")
 
     def test_constant_tuples(self):
-        self.check_src_roundtrip(ast.Module([ast.Constant(value=(1,))]), 
"(1,)")
+        locs = ast.fix_missing_locations
         self.check_src_roundtrip(
-            ast.Module([ast.Constant(value=(1, 2, 3))]), "(1, 2, 3)"
+            locs(ast.Module([ast.Expr(ast.Constant(value=(1,)))])), "(1,)")
+        self.check_src_roundtrip(
+            locs(ast.Module([ast.Expr(ast.Constant(value=(1, 2, 3)))])), "(1, 
2, 3)"
         )
 
     def test_function_type(self):
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-19-07-54.gh-issue-130080.IoJpuy.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-19-07-54.gh-issue-130080.IoJpuy.rst
new file mode 100644
index 00000000000000..7c9f30a9f973f6
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-19-07-54.gh-issue-130080.IoJpuy.rst
@@ -0,0 +1 @@
+Implement PEP 765: Disallow return/break/continue that exit a finally block.
diff --git a/Python/ast_opt.c b/Python/ast_opt.c
index e261f04361be5f..4a191e919e412c 100644
--- a/Python/ast_opt.c
+++ b/Python/ast_opt.c
@@ -1,15 +1,28 @@
 /* AST Optimizer */
 #include "Python.h"
 #include "pycore_ast.h"           // _PyAST_GetDocString()
+#include "pycore_c_array.h"       // _Py_CArray_EnsureCapacity()
 #include "pycore_format.h"        // F_LJUST
 #include "pycore_runtime.h"       // _Py_STR()
 #include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
 #include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
 
 
+/* See PEP 765 */
 typedef struct {
+    bool in_finally;
+    bool in_funcdef;
+    bool in_loop;
+} ControlFlowInFinallyContext;
+
+typedef struct {
+    PyObject *filename;
     int optimize;
     int ff_features;
+    int syntax_check_only;
+
+    _Py_c_array_t cf_finally;       /* context for PEP 678 check */
+    int cf_finally_used;
 } _PyASTOptimizeState;
 
 #define ENTER_RECURSIVE() \
@@ -19,6 +32,102 @@ if (Py_EnterRecursiveCall(" during compilation")) { \
 
 #define LEAVE_RECURSIVE() Py_LeaveRecursiveCall();
 
+static ControlFlowInFinallyContext*
+get_cf_finally_top(_PyASTOptimizeState *state)
+{
+    int idx = state->cf_finally_used;
+    return ((ControlFlowInFinallyContext*)state->cf_finally.array) + idx;
+}
+
+static int
+push_cf_context(_PyASTOptimizeState *state, stmt_ty node, bool finally, bool 
funcdef, bool loop)
+{
+    if (_Py_CArray_EnsureCapacity(&state->cf_finally, 
state->cf_finally_used+1) < 0) {
+        return 0;
+    }
+
+    state->cf_finally_used++;
+    ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
+
+    ctx->in_finally = finally;
+    ctx->in_funcdef = funcdef;
+    ctx->in_loop = loop;
+    return 1;
+}
+
+static void
+pop_cf_context(_PyASTOptimizeState *state)
+{
+    assert(state->cf_finally_used > 0);
+    state->cf_finally_used--;
+}
+
+static int
+control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTOptimizeState 
*state)
+{
+    PyObject *msg = PyUnicode_FromFormat("'%s' in a 'finally' block", kw);
+    if (msg == NULL) {
+        return 0;
+    }
+    int ret = _PyErr_EmitSyntaxWarning(msg, state->filename, n->lineno,
+                                       n->col_offset + 1, n->end_lineno,
+                                       n->end_col_offset + 1);
+    Py_DECREF(msg);
+    return ret < 0 ? 0 : 1;
+}
+
+static int
+before_return(_PyASTOptimizeState *state, stmt_ty node_)
+{
+    if (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)) {
+                return 0;
+            }
+        }
+    }
+    return 1;
+}
+
+static int
+before_loop_exit(_PyASTOptimizeState *state, stmt_ty node_, const char *kw)
+{
+    if (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)) {
+                return 0;
+            }
+        }
+    }
+    return 1;
+}
+
+#define PUSH_CONTEXT(S, N, FINALLY, FUNCDEF, LOOP) \
+    if (!push_cf_context((S), (N), (FINALLY), (FUNCDEF), (LOOP))) { \
+        return 0; \
+    }
+
+#define POP_CONTEXT(S) pop_cf_context(S)
+
+#define BEFORE_FINALLY(S, N)    PUSH_CONTEXT((S), (N), true, false, false)
+#define AFTER_FINALLY(S)        POP_CONTEXT(S)
+#define BEFORE_FUNC_BODY(S, N)  PUSH_CONTEXT((S), (N), false, true, false)
+#define AFTER_FUNC_BODY(S)      POP_CONTEXT(S)
+#define BEFORE_LOOP_BODY(S, N)  PUSH_CONTEXT((S), (N), false, false, true)
+#define AFTER_LOOP_BODY(S)      POP_CONTEXT(S)
+
+#define BEFORE_RETURN(S, N) \
+    if (!before_return((S), (N))) { \
+        return 0; \
+    }
+
+#define BEFORE_LOOP_EXIT(S, N, KW) \
+    if (!before_loop_exit((S), (N), (KW))) { \
+        return 0; \
+    }
+
 static int
 make_const(expr_ty node, PyObject *val, PyArena *arena)
 {
@@ -259,6 +368,9 @@ optimize_format(expr_ty node, PyObject *fmt, asdl_expr_seq 
*elts, PyArena *arena
 static int
 fold_binop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
 {
+    if (state->syntax_check_only) {
+        return 1;
+    }
     expr_ty lhs, rhs;
     lhs = node->v.BinOp.left;
     rhs = node->v.BinOp.right;
@@ -304,6 +416,9 @@ make_const_tuple(asdl_expr_seq *elts)
 static int
 fold_tuple(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
 {
+    if (state->syntax_check_only) {
+        return 1;
+    }
     PyObject *newval;
 
     if (node->v.Tuple.ctx != Load)
@@ -508,6 +623,9 @@ astfold_expr(expr_ty node_, PyArena *ctx_, 
_PyASTOptimizeState *state)
         CALL(fold_tuple, expr_ty, node_);
         break;
     case Name_kind:
+        if (state->syntax_check_only) {
+            break;
+        }
         if (node_->v.Name.ctx == Load &&
                 _PyUnicode_EqualToASCIIString(node_->v.Name.id, "__debug__")) {
             LEAVE_RECURSIVE();
@@ -570,24 +688,30 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, 
_PyASTOptimizeState *state)
 {
     ENTER_RECURSIVE();
     switch (node_->kind) {
-    case FunctionDef_kind:
+    case FunctionDef_kind: {
         CALL_SEQ(astfold_type_param, type_param, 
node_->v.FunctionDef.type_params);
         CALL(astfold_arguments, arguments_ty, node_->v.FunctionDef.args);
+        BEFORE_FUNC_BODY(state, node_);
         CALL(astfold_body, asdl_seq, node_->v.FunctionDef.body);
+        AFTER_FUNC_BODY(state);
         CALL_SEQ(astfold_expr, expr, node_->v.FunctionDef.decorator_list);
         if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
             CALL_OPT(astfold_expr, expr_ty, node_->v.FunctionDef.returns);
         }
         break;
-    case AsyncFunctionDef_kind:
+    }
+    case AsyncFunctionDef_kind: {
         CALL_SEQ(astfold_type_param, type_param, 
node_->v.AsyncFunctionDef.type_params);
         CALL(astfold_arguments, arguments_ty, node_->v.AsyncFunctionDef.args);
+        BEFORE_FUNC_BODY(state, node_);
         CALL(astfold_body, asdl_seq, node_->v.AsyncFunctionDef.body);
+        AFTER_FUNC_BODY(state);
         CALL_SEQ(astfold_expr, expr, node_->v.AsyncFunctionDef.decorator_list);
         if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
             CALL_OPT(astfold_expr, expr_ty, node_->v.AsyncFunctionDef.returns);
         }
         break;
+    }
     case ClassDef_kind:
         CALL_SEQ(astfold_type_param, type_param, 
node_->v.ClassDef.type_params);
         CALL_SEQ(astfold_expr, expr, node_->v.ClassDef.bases);
@@ -596,6 +720,7 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, 
_PyASTOptimizeState *state)
         CALL_SEQ(astfold_expr, expr, node_->v.ClassDef.decorator_list);
         break;
     case Return_kind:
+        BEFORE_RETURN(state, node_);
         CALL_OPT(astfold_expr, expr_ty, node_->v.Return.value);
         break;
     case Delete_kind:
@@ -621,23 +746,32 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, 
_PyASTOptimizeState *state)
         CALL_SEQ(astfold_type_param, type_param, 
node_->v.TypeAlias.type_params);
         CALL(astfold_expr, expr_ty, node_->v.TypeAlias.value);
         break;
-    case For_kind:
+    case For_kind: {
         CALL(astfold_expr, expr_ty, node_->v.For.target);
         CALL(astfold_expr, expr_ty, node_->v.For.iter);
+        BEFORE_LOOP_BODY(state, node_);
         CALL_SEQ(astfold_stmt, stmt, node_->v.For.body);
+        AFTER_LOOP_BODY(state);
         CALL_SEQ(astfold_stmt, stmt, node_->v.For.orelse);
         break;
-    case AsyncFor_kind:
+    }
+    case AsyncFor_kind: {
         CALL(astfold_expr, expr_ty, node_->v.AsyncFor.target);
         CALL(astfold_expr, expr_ty, node_->v.AsyncFor.iter);
+        BEFORE_LOOP_BODY(state, node_);
         CALL_SEQ(astfold_stmt, stmt, node_->v.AsyncFor.body);
+        AFTER_LOOP_BODY(state);
         CALL_SEQ(astfold_stmt, stmt, node_->v.AsyncFor.orelse);
         break;
-    case While_kind:
+    }
+    case While_kind: {
         CALL(astfold_expr, expr_ty, node_->v.While.test);
+        BEFORE_LOOP_BODY(state, node_);
         CALL_SEQ(astfold_stmt, stmt, node_->v.While.body);
+        AFTER_LOOP_BODY(state);
         CALL_SEQ(astfold_stmt, stmt, node_->v.While.orelse);
         break;
+    }
     case If_kind:
         CALL(astfold_expr, expr_ty, node_->v.If.test);
         CALL_SEQ(astfold_stmt, stmt, node_->v.If.body);
@@ -655,18 +789,24 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, 
_PyASTOptimizeState *state)
         CALL_OPT(astfold_expr, expr_ty, node_->v.Raise.exc);
         CALL_OPT(astfold_expr, expr_ty, node_->v.Raise.cause);
         break;
-    case Try_kind:
+    case Try_kind: {
         CALL_SEQ(astfold_stmt, stmt, node_->v.Try.body);
         CALL_SEQ(astfold_excepthandler, excepthandler, node_->v.Try.handlers);
         CALL_SEQ(astfold_stmt, stmt, node_->v.Try.orelse);
+        BEFORE_FINALLY(state, node_);
         CALL_SEQ(astfold_stmt, stmt, node_->v.Try.finalbody);
+        AFTER_FINALLY(state);
         break;
-    case TryStar_kind:
+    }
+    case TryStar_kind: {
         CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.body);
         CALL_SEQ(astfold_excepthandler, excepthandler, 
node_->v.TryStar.handlers);
         CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.orelse);
+        BEFORE_FINALLY(state, node_);
         CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.finalbody);
+        AFTER_FINALLY(state);
         break;
+    }
     case Assert_kind:
         CALL(astfold_expr, expr_ty, node_->v.Assert.test);
         CALL_OPT(astfold_expr, expr_ty, node_->v.Assert.msg);
@@ -678,14 +818,18 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, 
_PyASTOptimizeState *state)
         CALL(astfold_expr, expr_ty, node_->v.Match.subject);
         CALL_SEQ(astfold_match_case, match_case, node_->v.Match.cases);
         break;
+    case Break_kind:
+        BEFORE_LOOP_EXIT(state, node_, "break");
+        break;
+    case Continue_kind:
+        BEFORE_LOOP_EXIT(state, node_, "continue");
+        break;
     // The following statements don't contain any subexpressions to be folded
     case Import_kind:
     case ImportFrom_kind:
     case Global_kind:
     case Nonlocal_kind:
     case Pass_kind:
-    case Break_kind:
-    case Continue_kind:
         break;
     // No default case, so the compiler will emit a warning if new statement
     // kinds are added without being handled here
@@ -828,14 +972,22 @@ astfold_type_param(type_param_ty node_, PyArena *ctx_, 
_PyASTOptimizeState *stat
 #undef CALL_SEQ
 
 int
-_PyAST_Optimize(mod_ty mod, PyArena *arena, int optimize, int ff_features)
+_PyAST_Optimize(mod_ty mod, PyArena *arena, PyObject *filename, int optimize,
+                int ff_features, int syntax_check_only)
 {
     _PyASTOptimizeState state;
+    memset(&state, 0, sizeof(_PyASTOptimizeState));
+    state.filename = filename;
     state.optimize = optimize;
     state.ff_features = ff_features;
+    state.syntax_check_only = syntax_check_only;
+    if (_Py_CArray_Init(&state.cf_finally, 
sizeof(ControlFlowInFinallyContext), 20) < 0) {
+        return -1;
+    }
 
     int ret = astfold_mod(mod, arena, &state);
     assert(ret || PyErr_Occurred());
 
+    _Py_CArray_Fini(&state.cf_finally);
     return ret;
 }
diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
index 9f14d1745575d6..3174105fbe32e2 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -833,45 +833,35 @@ builtin_compile_impl(PyObject *module, PyObject *source, 
PyObject *filename,
     if (is_ast == -1)
         goto error;
     if (is_ast) {
-        if ((flags & PyCF_OPTIMIZED_AST) == PyCF_ONLY_AST) {
-            if (PyAst_CheckMode(source, compile_mode) < 0) {
-                goto error;
-            }
-            // return an un-optimized AST
-            result = Py_NewRef(source);
+        PyArena *arena = _PyArena_New();
+        if (arena == NULL) {
+            goto error;
         }
-        else {
-            // Return an optimized AST or code object
 
-            PyArena *arena = _PyArena_New();
-            if (arena == NULL) {
+        if (flags & PyCF_ONLY_AST) {
+            mod_ty mod = PyAST_obj2mod(source, arena, compile_mode);
+            if (mod == NULL || !_PyAST_Validate(mod)) {
+                _PyArena_Free(arena);
                 goto error;
             }
-
-            if (flags & PyCF_ONLY_AST) {
-                mod_ty mod = PyAST_obj2mod(source, arena, compile_mode);
-                if (mod == NULL || !_PyAST_Validate(mod)) {
-                    _PyArena_Free(arena);
-                    goto error;
-                }
-                if (_PyCompile_AstOptimize(mod, filename, &cf, optimize,
-                                           arena) < 0) {
-                    _PyArena_Free(arena);
-                    goto error;
-                }
-                result = PyAST_mod2obj(mod);
+            int syntax_check_only = ((flags & PyCF_OPTIMIZED_AST) == 
PyCF_ONLY_AST); /* unoptiomized AST */
+            if (_PyCompile_AstOptimize(mod, filename, &cf, optimize,
+                                           arena, syntax_check_only) < 0) {
+                _PyArena_Free(arena);
+                goto error;
             }
-            else {
-                mod_ty mod = PyAST_obj2mod(source, arena, compile_mode);
-                if (mod == NULL || !_PyAST_Validate(mod)) {
-                    _PyArena_Free(arena);
-                    goto error;
-                }
-                result = (PyObject*)_PyAST_Compile(mod, filename,
-                                                   &cf, optimize, arena);
+            result = PyAST_mod2obj(mod);
+        }
+        else {
+            mod_ty mod = PyAST_obj2mod(source, arena, compile_mode);
+            if (mod == NULL || !_PyAST_Validate(mod)) {
+                _PyArena_Free(arena);
+                goto error;
             }
-            _PyArena_Free(arena);
+            result = (PyObject*)_PyAST_Compile(mod, filename,
+                                               &cf, optimize, arena);
         }
+        _PyArena_Free(arena);
         goto finally;
     }
 
diff --git a/Python/compile.c b/Python/compile.c
index f5e2973436b65b..303d959c9d790c 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -131,7 +131,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_Optimize(mod, arena, c->c_optimize, merged)) {
+    if (!_PyAST_Optimize(mod, arena, filename, c->c_optimize, merged, 0)) {
         return ERROR;
     }
     c->c_st = _PySymtable_Build(mod, filename, &c->c_future);
@@ -1392,7 +1392,7 @@ _PyAST_Compile(mod_ty mod, PyObject *filename, 
PyCompilerFlags *pflags,
 
 int
 _PyCompile_AstOptimize(mod_ty mod, PyObject *filename, PyCompilerFlags *cf,
-                       int optimize, PyArena *arena)
+                       int optimize, PyArena *arena, int no_const_folding)
 {
     _PyFutureFeatures future;
     if (!_PyFuture_FromAST(mod, filename, &future)) {
@@ -1402,7 +1402,7 @@ _PyCompile_AstOptimize(mod_ty mod, PyObject *filename, 
PyCompilerFlags *cf,
     if (optimize == -1) {
         optimize = _Py_GetConfig()->optimization_level;
     }
-    if (!_PyAST_Optimize(mod, arena, optimize, flags)) {
+    if (!_PyAST_Optimize(mod, arena, filename, optimize, flags, 
no_const_folding)) {
         return -1;
     }
     return 0;
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
index 41f2174b2d6037..6e24131e5b1113 100644
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -1495,11 +1495,10 @@ Py_CompileStringObject(const char *str, PyObject 
*filename, int start,
         return NULL;
     }
     if (flags && (flags->cf_flags & PyCF_ONLY_AST)) {
-        if ((flags->cf_flags & PyCF_OPTIMIZED_AST) == PyCF_OPTIMIZED_AST) {
-            if (_PyCompile_AstOptimize(mod, filename, flags, optimize, arena) 
< 0) {
-                _PyArena_Free(arena);
-                return NULL;
-            }
+        int syntax_check_only = ((flags->cf_flags & PyCF_OPTIMIZED_AST) == 
PyCF_ONLY_AST); /* unoptiomized AST */
+        if (_PyCompile_AstOptimize(mod, filename, flags, optimize, arena, 
syntax_check_only) < 0) {
+            _PyArena_Free(arena);
+            return NULL;
         }
         PyObject *result = PyAST_mod2obj(mod);
         _PyArena_Free(arena);

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to