https://github.com/python/cpython/commit/ffc2f1dd1c023b44b488311511db790a96d757db
commit: ffc2f1dd1c023b44b488311511db790a96d757db
branch: main
author: Irit Katriel <[email protected]>
committer: iritkatriel <[email protected]>
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 -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]