Skip to content

Commit

Permalink
News, more fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra committed Jul 13, 2024
1 parent 1a39f45 commit 9a7ce67
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 40 deletions.
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ New Features
Other Language Changes
======================

* Incorrect usage of :keyword:`await` and asynchronous comprehensions
is now detected even if the code is optimized away by the :option:`-O`
command line option. For example, ``python -O -c 'assert await 1'``
now produces a syntax error. (Contributed by Jelle Zijlstra in :gh:`121637`.)


New Modules
Expand Down
56 changes: 27 additions & 29 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ def test_compile_top_level_await_no_coro(self):
"socket.accept is broken"
)
def test_compile_top_level_await(self):
"""Test whether code some top level await can be compiled.
"""Test whether code with top level await can be compiled.
Make sure it compiles only with the PyCF_ALLOW_TOP_LEVEL_AWAIT flag
set, and make sure the generated code object has the CO_COROUTINE flag
Expand Down Expand Up @@ -445,53 +445,51 @@ async def arange(n):
# 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 async for x in arange(1)}; a = 1''',
'''assert {x: x async for x in arange(1)}; a = 1''',
textwrap.dedent(
'''
if __debug__:
if (a := 1) and __debug__:
async with asyncio.Lock() as l:
pass
a = 1
'''
),
textwrap.dedent(
'''
if __debug__:
if (a := 1) and __debug__:
async for x in arange(2):
pass
a = 1
'''
),
]
policy = maybe_get_event_loop_policy()
try:
for mode, code_sample, optimize in product(modes, code_samples, optimizations):
source = dedent(code_sample)
with self.assertRaises(
SyntaxError, msg=f"source={source} mode={mode}"):
compile(source, '?', mode, optimize=optimize)
with self.subTest(mode=mode, code_sample=code_sample, optimize=optimize):
source = dedent(code_sample)
with self.assertRaises(
SyntaxError, msg=f"source={source} mode={mode}"):
compile(source, '?', mode, optimize=optimize)

co = compile(source,
'?',
mode,
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT,
optimize=optimize)

self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE,
msg=f"source={source} mode={mode}")

# test we can create and advance a function type
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
async_f = FunctionType(co, globals_)
asyncio.run(async_f())
self.assertEqual(globals_['a'], 1)

# test we can await-eval,
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
asyncio.run(eval(co, globals_))
self.assertEqual(globals_['a'], 1)
co = compile(source,
'?',
mode,
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT,
optimize=optimize)

self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE,
msg=f"source={source} mode={mode}")

# test we can create and advance a function type
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
async_f = FunctionType(co, globals_)
asyncio.run(async_f())
self.assertEqual(globals_['a'], 1)

# test we can await-eval,
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
asyncio.run(eval(co, globals_))
self.assertEqual(globals_['a'], 1)
finally:
asyncio.set_event_loop_policy(policy)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Previously, incorrect usage of :keyword:`await` or asynchronous
comprehensions in code removed by the :option:`-O` option was not flagged by
the Python compiler. Now, such codew raises :exc:`SyntaxError`. Patch by
Jelle Zijlstra.
17 changes: 6 additions & 11 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -5674,7 +5674,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
goto error;
}
int is_inlined = entry->ste_comp_inlined;
int is_async_generator = entry->ste_coroutine;
int is_async_comprehension = entry->ste_coroutine;

location loc = LOC(e);

Expand All @@ -5696,15 +5696,10 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
}
Py_CLEAR(entry);

if (is_async_generator && type != COMP_GENEXP &&
scope_type != COMPILER_SCOPE_ASYNC_FUNCTION &&
scope_type != COMPILER_SCOPE_COMPREHENSION &&
!is_top_level_await)
{
compiler_error(c, loc, "asynchronous comprehension outside of "
"an asynchronous function");
goto error_in_scope;
}
assert (!is_async_comprehension || type == COMP_GENEXP
|| scope_type == COMPILER_SCOPE_ASYNC_FUNCTION
|| scope_type == COMPILER_SCOPE_COMPREHENSION
|| is_top_level_await);

if (type != COMP_GENEXP) {
int op;
Expand Down Expand Up @@ -5769,7 +5764,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,

ADDOP_I(c, loc, CALL, 0);

if (is_async_generator && type != COMP_GENEXP) {
if (is_async_comprehension && type != COMP_GENEXP) {
ADDOP_I(c, loc, GET_AWAITABLE, 0);
ADDOP_LOAD_CONST(c, loc, Py_None);
ADD_YIELD_FROM(c, loc, 1);
Expand Down
9 changes: 9 additions & 0 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -2824,6 +2824,15 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
if (!symtable_exit_block(st)) {
return 0;
}
if (is_async &&
!(st->st_cur->ste_type == FunctionBlock && st->st_cur->ste_coroutine) &&
st->st_cur->ste_comprehension == NoComprehension &&
!allows_top_level_await(st)) {
PyErr_SetString(PyExc_SyntaxError, "asynchronous comprehension outside of "
"an asynchronous function");
SET_ERROR_LOCATION(st->st_filename, LOCATION(e));
return 0;
}
if (is_async) {
st->st_cur->ste_coroutine = 1;
}
Expand Down

0 comments on commit 9a7ce67

Please sign in to comment.