diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index da9b45cd8e58b3..cf4c23fa5b2267 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -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 diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 5001910eb9b98f..d777853cc5a563 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -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 @@ -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) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-07-12-18-18-44.gh-issue-121297.67VE7b.rst b/Misc/NEWS.d/next/Core and Builtins/2024-07-12-18-18-44.gh-issue-121297.67VE7b.rst new file mode 100644 index 00000000000000..b9e8f8409a0772 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-07-12-18-18-44.gh-issue-121297.67VE7b.rst @@ -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. diff --git a/Python/compile.c b/Python/compile.c index 36ef197153a57e..bae19be5d96079 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -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); @@ -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; @@ -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); diff --git a/Python/symtable.c b/Python/symtable.c index 457d749eb3748a..2f9e06348d11de 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -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; }