-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
gh-123958: move assert optimization from codegen to ast_opt #124143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -443,12 +443,6 @@ async def arange(n): | |
| '''a = [x async for x in (x async for x in arange(5))][1]''', | ||
| '''a, = [1 for x in {x async for x in arange(1)}]''', | ||
| '''a = [await asyncio.sleep(0, x) async for x in arange(2)][1]''', | ||
| # gh-121637: Make sure we correctly handle the case where the | ||
| # async code is optimized away | ||
| '''assert not await asyncio.sleep(0); a = 1''', | ||
| '''assert [x async for x in arange(1)]; a = 1''', | ||
| '''assert {x async for x in arange(1)}; a = 1''', | ||
| '''assert {x: x async for x in arange(1)}; a = 1''', | ||
| ''' | ||
| if (a := 1) and __debug__: | ||
| async with asyncio.Lock() as l: | ||
|
|
@@ -491,6 +485,29 @@ async def arange(n): | |
| finally: | ||
| asyncio.set_event_loop_policy(policy) | ||
|
|
||
| def test_top_level_async_in_assert_compiles(self): | ||
| # gh-121637: do the right thing when async code is optimized away | ||
| modes = ('single', 'exec') | ||
| optimizations = (0, 1, 2) | ||
| code_samples = [ | ||
| '''assert not await asyncio.sleep(0)''', | ||
| '''assert [x async for x in arange(1)]''', | ||
| '''assert {x async for x in arange(1)}''', | ||
| '''assert {x: x async for x in arange(1)}''', | ||
| ] | ||
| for mode, code_sample, optimize in product(modes, code_samples, optimizations): | ||
| with self.subTest(mode=mode, code_sample=code_sample, optimize=optimize): | ||
| source = dedent(code_sample) | ||
| if (optimize): | ||
| co = compile(source, '?', mode, optimize=optimize) | ||
| co_expected = compile('pass', '?', mode, optimize=optimize) | ||
|
iritkatriel marked this conversation as resolved.
Outdated
|
||
| self.assertEqual(list(co.co_lines()), list(co_expected.co_lines())) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like we no longer raise an error in optimized mode if there is an invalid await. That seems wrong and doesn't align with what we decided on in #121637.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want to stick with this then we probably can't optimise the assertions in ast_opt. |
||
| else: | ||
| with self.assertRaises(SyntaxError, | ||
| msg=f"source={source} mode={mode}"): | ||
| compile(source, '?', mode, optimize=optimize) | ||
|
|
||
|
|
||
| def test_compile_top_level_await_invalid_cases(self): | ||
| # helper function just to check we can run top=level async-for | ||
| async def arange(n): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| typedef struct { | ||
| int optimize; | ||
| int ff_features; | ||
| PyObject *filename; | ||
|
|
||
| int recursion_depth; /* current recursion depth */ | ||
| int recursion_limit; /* recursion limit */ | ||
|
|
@@ -51,6 +52,12 @@ make_const(expr_ty node, PyObject *val, PyArena *arena) | |
| return 1; | ||
| } | ||
|
|
||
| static void | ||
| make_pass(stmt_ty node) | ||
| { | ||
| node->kind = Pass_kind; | ||
| } | ||
|
|
||
| #define COPY_NODE(TO, FROM) (memcpy((TO), (FROM), sizeof(struct _expr))) | ||
|
|
||
| static int | ||
|
|
@@ -1005,6 +1012,29 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state) | |
| case Assert_kind: | ||
| CALL(astfold_expr, expr_ty, node_->v.Assert.test); | ||
| CALL_OPT(astfold_expr, expr_ty, node_->v.Assert.msg); | ||
| /* Always emit a warning if the test is a non-zero length tuple */ | ||
| if ((node_->v.Assert.test->kind == Tuple_kind && | ||
| asdl_seq_LEN(node_->v.Assert.test->v.Tuple.elts) > 0) || | ||
| (node_->v.Assert.test->kind == Constant_kind && | ||
| PyTuple_Check(node_->v.Assert.test->v.Constant.value) && | ||
| PyTuple_Size(node_->v.Assert.test->v.Constant.value) > 0)) | ||
| { | ||
| PyObject *msg = PyUnicode_FromString("assertion is always true, " | ||
| "perhaps remove parentheses?"); | ||
| if (msg == NULL) { | ||
| return 0; | ||
| } | ||
| int ret = _PyErr_EmitSyntaxWarning(msg, state->filename, | ||
| node_->lineno, node_->col_offset + 1, | ||
| node_->end_lineno, node_->end_col_offset + 1); | ||
| Py_DECREF(msg); | ||
| if (ret < 0) { | ||
| return 0; | ||
| } | ||
| } | ||
| if (state->optimize) { | ||
| make_pass(node_); | ||
|
Comment on lines
+1035
to
+1036
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically speaking, this is not the removal of the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pass becomes a NOP, which gets removed later. This is how we get rid of code. However, I did forget to wipe out the location information (a NOP is not removed if it's the only instruction from its line in the source code). |
||
| } | ||
| break; | ||
| case Expr_kind: | ||
| CALL(astfold_expr, expr_ty, node_->v.Expr.value); | ||
|
|
@@ -1125,14 +1155,15 @@ 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, int optimize, int ff_features, PyObject *filename) | ||
| { | ||
| PyThreadState *tstate; | ||
| int starting_recursion_depth; | ||
|
|
||
| _PyASTOptimizeState state; | ||
| state.optimize = optimize; | ||
| state.ff_features = ff_features; | ||
| state.filename = filename; | ||
|
|
||
| /* Setup recursion depth check counters */ | ||
| tstate = _PyThreadState_GET(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2792,20 +2792,7 @@ codegen_from_import(compiler *c, stmt_ty s) | |
| static int | ||
| codegen_assert(compiler *c, stmt_ty s) | ||
| { | ||
| /* Always emit a warning if the test is a non-zero length tuple */ | ||
| if ((s->v.Assert.test->kind == Tuple_kind && | ||
| asdl_seq_LEN(s->v.Assert.test->v.Tuple.elts) > 0) || | ||
| (s->v.Assert.test->kind == Constant_kind && | ||
| PyTuple_Check(s->v.Assert.test->v.Constant.value) && | ||
| PyTuple_Size(s->v.Assert.test->v.Constant.value) > 0)) | ||
| { | ||
| RETURN_IF_ERROR( | ||
| _PyCompile_Warn(c, LOC(s), "assertion is always true, " | ||
| "perhaps remove parentheses?")); | ||
| } | ||
| if (OPTIMIZATION_LEVEL(c)) { | ||
| return SUCCESS; | ||
| } | ||
| assert(!OPTIMIZATION_LEVEL(c)); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Left this here to convince ourselves of correctness. I'll remove it before committing. |
||
| NEW_JUMP_TARGET_LABEL(c, end); | ||
| RETURN_IF_ERROR(codegen_jump_if(c, LOC(s), s->v.Assert.test, end, 1)); | ||
| ADDOP_I(c, LOC(s), LOAD_COMMON_CONSTANT, CONSTANT_ASSERTIONERROR); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.