gh-130080: implement PEP 765 (#130087)

This commit is contained in:
Irit Katriel 2025-03-17 20:48:54 +00:00 committed by GitHub
parent 468a7aaeb4
commit ffc2f1dd1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 426 additions and 109 deletions

View 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 :keyword:`!finally` clause. If the :keyword:`!finally` clause raises another
exception, the saved exception is set as the context of the new exception. exception, the saved exception is set as the context of the new exception.
If the :keyword:`!finally` clause executes a :keyword:`return`, :keyword:`break` 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(): .. code-block::
... try:
... 1/0 def f():
... finally: try:
... return 42 1/0
... finally:
>>> f() return 42
42
The exception information is not available to the program during execution of The exception information is not available to the program during execution of
the :keyword:`!finally` clause. 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` The return value of a function is determined by the last :keyword:`return`
statement executed. Since the :keyword:`!finally` clause always executes, a statement executed. Since the :keyword:`!finally` clause always executes, a
:keyword:`!return` statement executed in the :keyword:`!finally` clause will :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(): .. code-block::
... try:
... return 'try' def foo():
... finally: try:
... return 'finally' return 'try'
... finally:
>>> foo() return 'finally'
'finally'
.. versionchanged:: 3.8 .. versionchanged:: 3.8
Prior to Python 3.8, a :keyword:`continue` statement was illegal in the Prior to Python 3.8, a :keyword:`continue` statement was illegal in the
:keyword:`!finally` clause due to a problem with the implementation. :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: .. _with:
.. _as: .. _as:

View file

@ -418,7 +418,9 @@ points discuss more complex cases when an exception occurs:
* If the :keyword:`!finally` clause executes a :keyword:`break`, * If the :keyword:`!finally` clause executes a :keyword:`break`,
:keyword:`continue` or :keyword:`return` statement, exceptions are not :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`, * If the :keyword:`!try` statement reaches a :keyword:`break`,
:keyword:`continue` or :keyword:`return` statement, the :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 statement, the returned value will be the one from the
:keyword:`!finally` clause's :keyword:`!return` statement, not the :keyword:`!finally` clause's :keyword:`!return` statement, not the
value from the :keyword:`!try` clause's :keyword:`!return` 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:: For example::

View file

@ -68,6 +68,7 @@ Summary -- release highlights
* :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>` * :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>`
* :ref:`PEP 761: Discontinuation of PGP signatures <whatsnew314-pep761>` * :ref:`PEP 761: Discontinuation of PGP signatures <whatsnew314-pep761>`
* :ref:`A new type of interpreter <whatsnew314-tail-call>` * :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 Incompatible changes
@ -370,6 +371,15 @@ Other language changes
The testbed can also be used to run the test suite of projects other than 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`.) 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 New modules
=========== ===========

View file

@ -40,13 +40,16 @@ extern int _PyCompile_AstOptimize(
PyObject *filename, PyObject *filename,
PyCompilerFlags *flags, PyCompilerFlags *flags,
int optimize, int optimize,
struct _arena *arena); struct _arena *arena,
int syntax_check_only);
extern int _PyAST_Optimize( extern int _PyAST_Optimize(
struct _mod *, struct _mod *,
struct _arena *arena, struct _arena *arena,
PyObject *filename,
int optimize, int optimize,
int ff_features); int ff_features,
int syntax_check_only);
typedef struct { typedef struct {

View file

@ -37,6 +37,7 @@ class AllTest(unittest.TestCase):
(".* (module|package)", DeprecationWarning), (".* (module|package)", DeprecationWarning),
(".* (module|package)", PendingDeprecationWarning), (".* (module|package)", PendingDeprecationWarning),
("", ResourceWarning), ("", ResourceWarning),
("", SyntaxWarning),
quiet=True): quiet=True):
try: try:
exec("import %s" % modname, names) exec("import %s" % modname, names)
@ -52,6 +53,7 @@ class AllTest(unittest.TestCase):
with warnings_helper.check_warnings( with warnings_helper.check_warnings(
("", DeprecationWarning), ("", DeprecationWarning),
("", ResourceWarning), ("", ResourceWarning),
("", SyntaxWarning),
quiet=True): quiet=True):
try: try:
exec("from %s import *" % modname, names) exec("from %s import *" % modname, names)

View file

@ -820,6 +820,61 @@ class AST_Tests(unittest.TestCase):
r"Exceeds the limit \(\d+ digits\)"): r"Exceeds the limit \(\d+ digits\)"):
repr(ast.Constant(value=eval(source))) 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): class CopyTests(unittest.TestCase):
"""Test copying and pickling AST nodes.""" """Test copying and pickling AST nodes."""

View file

@ -84,7 +84,8 @@ class TestBreakContinueReturnInExceptStarBlock(unittest.TestCase):
if i == 2: if i == 2:
break break
finally: finally:
return 0 pass
return 0
""") """)
@ -117,7 +118,8 @@ class TestBreakContinueReturnInExceptStarBlock(unittest.TestCase):
if i == 2: if i == 2:
continue continue
finally: finally:
return 0 pass
return 0
""") """)
def test_return_in_except_star_block_invalid(self): def test_return_in_except_star_block_invalid(self):

View file

@ -858,7 +858,7 @@ Traceback (most recent call last):
SyntaxError: 'function call' is an illegal expression for augmented assignment 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. continue in for loop under finally should be ok.
@ -872,51 +872,63 @@ continue in for loop under finally should be ok.
>>> test() >>> test()
9 9
continue in a finally should be ok. break in for loop under finally should be ok.
>>> def test(): >>> def test():
... for abc in range(10): ... try:
... try: ... pass
... pass ... finally:
... finally: ... for abc in range(10):
... continue ... break
... print(abc) ... print(abc)
>>> test() >>> test()
9 0
return in function under finally should be ok.
>>> def test(): >>> def test():
... for abc in range(10): ... try:
... try: ... pass
... pass ... finally:
... finally: ... def f():
... try: ... return 42
... continue ... print(f())
... except:
... pass
... print(abc)
>>> test() >>> test()
9 42
combine for loop and function def
return in function under finally should be ok.
>>> def test(): >>> def test():
... for abc in range(10): ... try:
... try: ... pass
... pass ... finally:
... finally: ... for i in range(10):
... try: ... def f():
... pass ... return 42
... except: ... print(f())
... continue
... print(abc)
>>> test() >>> 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. A continue outside loop should not be allowed.
>>> def foo(): >>> def foo():
... try: ... try:
... pass
... finally:
... continue ... continue
... finally:
... pass
Traceback (most recent call last): Traceback (most recent call last):
... ...
SyntaxError: 'continue' not properly in loop SyntaxError: 'continue' not properly in loop
@ -2393,7 +2405,88 @@ import unittest
from test import support 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, def _check_error(self, code, errtext,
filename="<testcase>", mode="exec", subclass=None, filename="<testcase>", mode="exec", subclass=None,
@ -2401,7 +2494,7 @@ class SyntaxTestCase(unittest.TestCase):
"""Check that compiling code raises SyntaxError with errtext. """Check that compiling code raises SyntaxError with errtext.
errtest is a regular expression that must be present in the 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). is the expected subclass of SyntaxError (e.g. IndentationError).
""" """
try: try:

View file

@ -422,9 +422,11 @@ class UnparseTestCase(ASTTestCase):
self.check_ast_roundtrip(f"'''{docstring}'''") self.check_ast_roundtrip(f"'''{docstring}'''")
def test_constant_tuples(self): 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( 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): def test_function_type(self):

View file

@ -0,0 +1 @@
Implement PEP 765: Disallow return/break/continue that exit a finally block.

View file

@ -1,15 +1,28 @@
/* AST Optimizer */ /* AST Optimizer */
#include "Python.h" #include "Python.h"
#include "pycore_ast.h" // _PyAST_GetDocString() #include "pycore_ast.h" // _PyAST_GetDocString()
#include "pycore_c_array.h" // _Py_CArray_EnsureCapacity()
#include "pycore_format.h" // F_LJUST #include "pycore_format.h" // F_LJUST
#include "pycore_runtime.h" // _Py_STR() #include "pycore_runtime.h" // _Py_STR()
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString() #include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString() #include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
/* See PEP 765 */
typedef struct { typedef struct {
bool in_finally;
bool in_funcdef;
bool in_loop;
} ControlFlowInFinallyContext;
typedef struct {
PyObject *filename;
int optimize; int optimize;
int ff_features; int ff_features;
int syntax_check_only;
_Py_c_array_t cf_finally; /* context for PEP 678 check */
int cf_finally_used;
} _PyASTOptimizeState; } _PyASTOptimizeState;
#define ENTER_RECURSIVE() \ #define ENTER_RECURSIVE() \
@ -19,6 +32,102 @@ if (Py_EnterRecursiveCall(" during compilation")) { \
#define LEAVE_RECURSIVE() Py_LeaveRecursiveCall(); #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 static int
make_const(expr_ty node, PyObject *val, PyArena *arena) 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 static int
fold_binop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state) fold_binop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
{ {
if (state->syntax_check_only) {
return 1;
}
expr_ty lhs, rhs; expr_ty lhs, rhs;
lhs = node->v.BinOp.left; lhs = node->v.BinOp.left;
rhs = node->v.BinOp.right; rhs = node->v.BinOp.right;
@ -304,6 +416,9 @@ make_const_tuple(asdl_expr_seq *elts)
static int static int
fold_tuple(expr_ty node, PyArena *arena, _PyASTOptimizeState *state) fold_tuple(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
{ {
if (state->syntax_check_only) {
return 1;
}
PyObject *newval; PyObject *newval;
if (node->v.Tuple.ctx != Load) 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_); CALL(fold_tuple, expr_ty, node_);
break; break;
case Name_kind: case Name_kind:
if (state->syntax_check_only) {
break;
}
if (node_->v.Name.ctx == Load && if (node_->v.Name.ctx == Load &&
_PyUnicode_EqualToASCIIString(node_->v.Name.id, "__debug__")) { _PyUnicode_EqualToASCIIString(node_->v.Name.id, "__debug__")) {
LEAVE_RECURSIVE(); LEAVE_RECURSIVE();
@ -570,24 +688,30 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{ {
ENTER_RECURSIVE(); ENTER_RECURSIVE();
switch (node_->kind) { switch (node_->kind) {
case FunctionDef_kind: case FunctionDef_kind: {
CALL_SEQ(astfold_type_param, type_param, node_->v.FunctionDef.type_params); CALL_SEQ(astfold_type_param, type_param, node_->v.FunctionDef.type_params);
CALL(astfold_arguments, arguments_ty, node_->v.FunctionDef.args); CALL(astfold_arguments, arguments_ty, node_->v.FunctionDef.args);
BEFORE_FUNC_BODY(state, node_);
CALL(astfold_body, asdl_seq, node_->v.FunctionDef.body); CALL(astfold_body, asdl_seq, node_->v.FunctionDef.body);
AFTER_FUNC_BODY(state);
CALL_SEQ(astfold_expr, expr, node_->v.FunctionDef.decorator_list); CALL_SEQ(astfold_expr, expr, node_->v.FunctionDef.decorator_list);
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) { if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL_OPT(astfold_expr, expr_ty, node_->v.FunctionDef.returns); CALL_OPT(astfold_expr, expr_ty, node_->v.FunctionDef.returns);
} }
break; break;
case AsyncFunctionDef_kind: }
case AsyncFunctionDef_kind: {
CALL_SEQ(astfold_type_param, type_param, node_->v.AsyncFunctionDef.type_params); CALL_SEQ(astfold_type_param, type_param, node_->v.AsyncFunctionDef.type_params);
CALL(astfold_arguments, arguments_ty, node_->v.AsyncFunctionDef.args); CALL(astfold_arguments, arguments_ty, node_->v.AsyncFunctionDef.args);
BEFORE_FUNC_BODY(state, node_);
CALL(astfold_body, asdl_seq, node_->v.AsyncFunctionDef.body); CALL(astfold_body, asdl_seq, node_->v.AsyncFunctionDef.body);
AFTER_FUNC_BODY(state);
CALL_SEQ(astfold_expr, expr, node_->v.AsyncFunctionDef.decorator_list); CALL_SEQ(astfold_expr, expr, node_->v.AsyncFunctionDef.decorator_list);
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) { if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL_OPT(astfold_expr, expr_ty, node_->v.AsyncFunctionDef.returns); CALL_OPT(astfold_expr, expr_ty, node_->v.AsyncFunctionDef.returns);
} }
break; break;
}
case ClassDef_kind: case ClassDef_kind:
CALL_SEQ(astfold_type_param, type_param, node_->v.ClassDef.type_params); CALL_SEQ(astfold_type_param, type_param, node_->v.ClassDef.type_params);
CALL_SEQ(astfold_expr, expr, node_->v.ClassDef.bases); 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); CALL_SEQ(astfold_expr, expr, node_->v.ClassDef.decorator_list);
break; break;
case Return_kind: case Return_kind:
BEFORE_RETURN(state, node_);
CALL_OPT(astfold_expr, expr_ty, node_->v.Return.value); CALL_OPT(astfold_expr, expr_ty, node_->v.Return.value);
break; break;
case Delete_kind: 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_SEQ(astfold_type_param, type_param, node_->v.TypeAlias.type_params);
CALL(astfold_expr, expr_ty, node_->v.TypeAlias.value); CALL(astfold_expr, expr_ty, node_->v.TypeAlias.value);
break; break;
case For_kind: case For_kind: {
CALL(astfold_expr, expr_ty, node_->v.For.target); CALL(astfold_expr, expr_ty, node_->v.For.target);
CALL(astfold_expr, expr_ty, node_->v.For.iter); CALL(astfold_expr, expr_ty, node_->v.For.iter);
BEFORE_LOOP_BODY(state, node_);
CALL_SEQ(astfold_stmt, stmt, node_->v.For.body); CALL_SEQ(astfold_stmt, stmt, node_->v.For.body);
AFTER_LOOP_BODY(state);
CALL_SEQ(astfold_stmt, stmt, node_->v.For.orelse); CALL_SEQ(astfold_stmt, stmt, node_->v.For.orelse);
break; break;
case AsyncFor_kind: }
case AsyncFor_kind: {
CALL(astfold_expr, expr_ty, node_->v.AsyncFor.target); CALL(astfold_expr, expr_ty, node_->v.AsyncFor.target);
CALL(astfold_expr, expr_ty, node_->v.AsyncFor.iter); CALL(astfold_expr, expr_ty, node_->v.AsyncFor.iter);
BEFORE_LOOP_BODY(state, node_);
CALL_SEQ(astfold_stmt, stmt, node_->v.AsyncFor.body); CALL_SEQ(astfold_stmt, stmt, node_->v.AsyncFor.body);
AFTER_LOOP_BODY(state);
CALL_SEQ(astfold_stmt, stmt, node_->v.AsyncFor.orelse); CALL_SEQ(astfold_stmt, stmt, node_->v.AsyncFor.orelse);
break; break;
case While_kind: }
case While_kind: {
CALL(astfold_expr, expr_ty, node_->v.While.test); CALL(astfold_expr, expr_ty, node_->v.While.test);
BEFORE_LOOP_BODY(state, node_);
CALL_SEQ(astfold_stmt, stmt, node_->v.While.body); CALL_SEQ(astfold_stmt, stmt, node_->v.While.body);
AFTER_LOOP_BODY(state);
CALL_SEQ(astfold_stmt, stmt, node_->v.While.orelse); CALL_SEQ(astfold_stmt, stmt, node_->v.While.orelse);
break; break;
}
case If_kind: case If_kind:
CALL(astfold_expr, expr_ty, node_->v.If.test); CALL(astfold_expr, expr_ty, node_->v.If.test);
CALL_SEQ(astfold_stmt, stmt, node_->v.If.body); 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.exc);
CALL_OPT(astfold_expr, expr_ty, node_->v.Raise.cause); CALL_OPT(astfold_expr, expr_ty, node_->v.Raise.cause);
break; break;
case Try_kind: case Try_kind: {
CALL_SEQ(astfold_stmt, stmt, node_->v.Try.body); CALL_SEQ(astfold_stmt, stmt, node_->v.Try.body);
CALL_SEQ(astfold_excepthandler, excepthandler, node_->v.Try.handlers); CALL_SEQ(astfold_excepthandler, excepthandler, node_->v.Try.handlers);
CALL_SEQ(astfold_stmt, stmt, node_->v.Try.orelse); CALL_SEQ(astfold_stmt, stmt, node_->v.Try.orelse);
BEFORE_FINALLY(state, node_);
CALL_SEQ(astfold_stmt, stmt, node_->v.Try.finalbody); CALL_SEQ(astfold_stmt, stmt, node_->v.Try.finalbody);
AFTER_FINALLY(state);
break; break;
case TryStar_kind: }
case TryStar_kind: {
CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.body); CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.body);
CALL_SEQ(astfold_excepthandler, excepthandler, node_->v.TryStar.handlers); CALL_SEQ(astfold_excepthandler, excepthandler, node_->v.TryStar.handlers);
CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.orelse); CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.orelse);
BEFORE_FINALLY(state, node_);
CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.finalbody); CALL_SEQ(astfold_stmt, stmt, node_->v.TryStar.finalbody);
AFTER_FINALLY(state);
break; break;
}
case Assert_kind: case Assert_kind:
CALL(astfold_expr, expr_ty, node_->v.Assert.test); CALL(astfold_expr, expr_ty, node_->v.Assert.test);
CALL_OPT(astfold_expr, expr_ty, node_->v.Assert.msg); 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(astfold_expr, expr_ty, node_->v.Match.subject);
CALL_SEQ(astfold_match_case, match_case, node_->v.Match.cases); CALL_SEQ(astfold_match_case, match_case, node_->v.Match.cases);
break; 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 // The following statements don't contain any subexpressions to be folded
case Import_kind: case Import_kind:
case ImportFrom_kind: case ImportFrom_kind:
case Global_kind: case Global_kind:
case Nonlocal_kind: case Nonlocal_kind:
case Pass_kind: case Pass_kind:
case Break_kind:
case Continue_kind:
break; break;
// No default case, so the compiler will emit a warning if new statement // No default case, so the compiler will emit a warning if new statement
// kinds are added without being handled here // 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 #undef CALL_SEQ
int 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; _PyASTOptimizeState state;
memset(&state, 0, sizeof(_PyASTOptimizeState));
state.filename = filename;
state.optimize = optimize; state.optimize = optimize;
state.ff_features = ff_features; 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); int ret = astfold_mod(mod, arena, &state);
assert(ret || PyErr_Occurred()); assert(ret || PyErr_Occurred());
_Py_CArray_Fini(&state.cf_finally);
return ret; return ret;
} }

View file

@ -833,45 +833,35 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
if (is_ast == -1) if (is_ast == -1)
goto error; goto error;
if (is_ast) { if (is_ast) {
if ((flags & PyCF_OPTIMIZED_AST) == PyCF_ONLY_AST) { PyArena *arena = _PyArena_New();
if (PyAst_CheckMode(source, compile_mode) < 0) { if (arena == NULL) {
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; goto error;
} }
// return an un-optimized AST int syntax_check_only = ((flags & PyCF_OPTIMIZED_AST) == PyCF_ONLY_AST); /* unoptiomized AST */
result = Py_NewRef(source); if (_PyCompile_AstOptimize(mod, filename, &cf, optimize,
arena, syntax_check_only) < 0) {
_PyArena_Free(arena);
goto error;
}
result = PyAST_mod2obj(mod);
} }
else { else {
// Return an optimized AST or code object mod_ty mod = PyAST_obj2mod(source, arena, compile_mode);
if (mod == NULL || !_PyAST_Validate(mod)) {
PyArena *arena = _PyArena_New(); _PyArena_Free(arena);
if (arena == NULL) {
goto error; goto error;
} }
result = (PyObject*)_PyAST_Compile(mod, filename,
if (flags & PyCF_ONLY_AST) { &cf, optimize, arena);
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);
}
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);
}
_PyArena_Free(arena);
} }
_PyArena_Free(arena);
goto finally; goto finally;
} }

View 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_optimize = (optimize == -1) ? _Py_GetConfig()->optimization_level : optimize;
c->c_save_nested_seqs = false; 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; return ERROR;
} }
c->c_st = _PySymtable_Build(mod, filename, &c->c_future); c->c_st = _PySymtable_Build(mod, filename, &c->c_future);
@ -1392,7 +1392,7 @@ _PyAST_Compile(mod_ty mod, PyObject *filename, PyCompilerFlags *pflags,
int int
_PyCompile_AstOptimize(mod_ty mod, PyObject *filename, PyCompilerFlags *cf, _PyCompile_AstOptimize(mod_ty mod, PyObject *filename, PyCompilerFlags *cf,
int optimize, PyArena *arena) int optimize, PyArena *arena, int no_const_folding)
{ {
_PyFutureFeatures future; _PyFutureFeatures future;
if (!_PyFuture_FromAST(mod, filename, &future)) { if (!_PyFuture_FromAST(mod, filename, &future)) {
@ -1402,7 +1402,7 @@ _PyCompile_AstOptimize(mod_ty mod, PyObject *filename, PyCompilerFlags *cf,
if (optimize == -1) { if (optimize == -1) {
optimize = _Py_GetConfig()->optimization_level; 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 -1;
} }
return 0; return 0;

View file

@ -1495,11 +1495,10 @@ Py_CompileStringObject(const char *str, PyObject *filename, int start,
return NULL; return NULL;
} }
if (flags && (flags->cf_flags & PyCF_ONLY_AST)) { if (flags && (flags->cf_flags & PyCF_ONLY_AST)) {
if ((flags->cf_flags & PyCF_OPTIMIZED_AST) == PyCF_OPTIMIZED_AST) { int syntax_check_only = ((flags->cf_flags & PyCF_OPTIMIZED_AST) == PyCF_ONLY_AST); /* unoptiomized AST */
if (_PyCompile_AstOptimize(mod, filename, flags, optimize, arena) < 0) { if (_PyCompile_AstOptimize(mod, filename, flags, optimize, arena, syntax_check_only) < 0) {
_PyArena_Free(arena); _PyArena_Free(arena);
return NULL; return NULL;
}
} }
PyObject *result = PyAST_mod2obj(mod); PyObject *result = PyAST_mod2obj(mod);
_PyArena_Free(arena); _PyArena_Free(arena);