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