]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-130080: implement PEP 765 (#130087)
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>
Mon, 17 Mar 2025 20:48:54 +0000 (20:48 +0000)
committerGitHub <noreply@github.com>
Mon, 17 Mar 2025 20:48:54 +0000 (20:48 +0000)
14 files changed:
Doc/reference/compound_stmts.rst
Doc/tutorial/errors.rst
Doc/whatsnew/3.14.rst
Include/internal/pycore_compile.h
Lib/test/test___all__.py
Lib/test/test_ast/test_ast.py
Lib/test/test_except_star.py
Lib/test/test_syntax.py
Lib/test/test_unparse.py
Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-19-07-54.gh-issue-130080.IoJpuy.rst [new file with mode: 0644]
Python/ast_opt.c
Python/bltinmodule.c
Python/compile.c
Python/pythonrun.c

index 71cc0c83de567e07d3a89423f03da9817cbb9cd4..949cdf3be8b7e3a151a0efbe33eac04c72ca09ee 100644 (file)
@@ -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:
index bfb281c1b7d66a7fd81af9c3e020c23ee8f70a5e..1c20fa2f0b6ae588ab85338da29e6fda50948abe 100644 (file)
@@ -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::
 
index b133719063652906f0204ae8c386579a85f4d132..789156974cb0d18ae49a31dc76c5801cc1433771 100644 (file)
@@ -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
 ===========
 
index 5b08bb6f8a71352a83f4a670e25e74e82797f356..467374c705486ea6e955e0345d8d115cfa07a786 100644 (file)
@@ -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 {
index e405056c8ffcb5c5fa7f198b333ff2e97a481bde..f35b1194308262257db23556788b13c2aab03dfc 100644 (file)
@@ -37,6 +37,7 @@ class AllTest(unittest.TestCase):
             (".* (module|package)", DeprecationWarning),
             (".* (module|package)", PendingDeprecationWarning),
             ("", ResourceWarning),
+            ("", SyntaxWarning),
             quiet=True):
             try:
                 exec("import %s" % modname, names)
@@ -52,6 +53,7 @@ class AllTest(unittest.TestCase):
             with warnings_helper.check_warnings(
                 ("", DeprecationWarning),
                 ("", ResourceWarning),
+                ("", SyntaxWarning),
                 quiet=True):
                 try:
                     exec("from %s import *" % modname, names)
index 6e1458facafc30fe2de75a51c050c28fede1ac12..e63ddb7d1fecc4cb8d3815af8ca8a3869a5d8237 100644 (file)
@@ -820,6 +820,61 @@ 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)
+
 
 class CopyTests(unittest.TestCase):
     """Test copying and pickling AST nodes."""
index 284907f61213f8abe66be078f4e81fa730779618..47006c6d3a0c36a1ffd2913e41f98ecfec3e57ab 100644 (file)
@@ -84,7 +84,8 @@ class TestBreakContinueReturnInExceptStarBlock(unittest.TestCase):
                     if i == 2:
                         break
                 finally:
-                    return 0
+                    pass
+                return 0
             """)
 
 
@@ -117,7 +118,8 @@ class TestBreakContinueReturnInExceptStarBlock(unittest.TestCase):
                     if i == 2:
                         continue
                 finally:
-                    return 0
+                    pass
+                return 0
             """)
 
     def test_return_in_except_star_block_invalid(self):
index 707d4fc6df16ea31c055695d043413111aa21c75..d2950cf48abb21b86e9065a35b82ccd159f4f089 100644 (file)
@@ -858,7 +858,7 @@ Traceback (most recent call last):
 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 @@ continue in for loop under finally should be ok.
     >>> 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 @@ import unittest
 
 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 @@ class SyntaxTestCase(unittest.TestCase):
         """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:
index f45a651c7ccb5d555ad828076ab35de37e724c5d..9efea1e037f44764a55884bcaff13324d50d7399 100644 (file)
@@ -422,9 +422,11 @@ class UnparseTestCase(ASTTestCase):
             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 (file)
index 0000000..7c9f30a
--- /dev/null
@@ -0,0 +1 @@
+Implement PEP 765: Disallow return/break/continue that exit a finally block.
index e261f04361be5f4d2c162b77c0b02b413757160d..4a191e919e412c02684036f89a72cb445340b4c4 100644 (file)
@@ -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;
 }
index 9f14d1745575d63023c5cc351f72d2a589e8c477..3174105fbe32e233389b5884ce1e9dac4850ccd0 100644 (file)
@@ -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;
     }
 
index f5e2973436b65b2b7f8af54b53f9fee273e8bc28..303d959c9d790c26c7515853b4e76edffedc0433 100644 (file)
@@ -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;
index 41f2174b2d60376ccb6c06071e6df1be9d22dda7..6e24131e5b1113c47401e775ae253d99371df59e 100644 (file)
@@ -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);