Skip to content

Commit e55256f

Browse files
author
Matthias Bussonnier
committed
Add option to compile() to accept top-level await features.
In some narrow but rate case; it is useful to be allow to compile code with top-level await features; for example when writing a REPL. This attempt to allow that.
1 parent ad2ed0a commit e55256f

File tree

7 files changed

+61
-15
lines changed

7 files changed

+61
-15
lines changed

Doc/library/functions.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@ are always available. They are listed here in alphabetical order.
257257
can be found as the :attr:`~__future__._Feature.compiler_flag` attribute on
258258
the :class:`~__future__._Feature` instance in the :mod:`__future__` module.
259259

260+
The optional argument *flags* also control wether the compiled source is
261+
allowed to contain top-level ``await``, ``async for`` and ``async with``.
262+
When the bit ``ast.PyCF_ALLOW_TOP_LEVEL_AWAIT`` is set, the the return code
263+
object has ``CO_COROUTINE`` set in ``co_code``, and can be interacitvely
264+
executed via ``yield from eval(code_object)``.
265+
260266
The argument *optimize* specifies the optimization level of the compiler; the
261267
default value of ``-1`` selects the optimization level of the interpreter as
262268
given by :option:`-O` options. Explicit levels are ``0`` (no optimization;
@@ -290,6 +296,10 @@ are always available. They are listed here in alphabetical order.
290296
Previously, :exc:`TypeError` was raised when null bytes were encountered
291297
in *source*.
292298

299+
.. versionadded:: 3.8
300+
*flags* can now be used to accept source that would contain a top-level
301+
``await``, ``async for`` or ``async with``.
302+
293303

294304
.. class:: complex([real[, imag]])
295305

Include/compile.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ PyAPI_FUNC(PyCodeObject *) PyNode_Compile(struct _node *, const char *);
2323
#define PyCF_ONLY_AST 0x0400
2424
#define PyCF_IGNORE_COOKIE 0x0800
2525
#define PyCF_TYPE_COMMENTS 0x1000
26-
#define PyCF_AWAIT 0x2000
26+
#define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000
2727

2828
#ifndef Py_LIMITED_API
2929
typedef struct {

Lib/test/test_builtin.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import unittest
1919
import warnings
2020
from contextlib import ExitStack
21+
from inspect import CO_COROUTINE
22+
from textwrap import dedent
2123
from operator import neg
2224
from test.support import (
2325
EnvironmentVarGuard, TESTFN, check_warnings, swap_attr, unlink)
@@ -358,6 +360,33 @@ def f(): """doc"""
358360
rv = ns['f']()
359361
self.assertEqual(rv, tuple(expected))
360362

363+
def test_compile_top_level_await(self):
364+
for mode in ('single', 'exec'):
365+
for num,code_sample in enumerate(['''await sleep(0)''',
366+
'''async for i in range(10):
367+
print(i)''',
368+
'''async with asyncio.Lock() as l:
369+
pass''']):
370+
source = dedent(code_sample)
371+
with self.assertRaises(SyntaxError,
372+
msg='source={!r} mode={!r})'.format(source, mode)):
373+
compile(source, '?' , mode)
374+
co = compile(source, '?', mode, flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
375+
self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE)
376+
377+
def test_compile_async_generator(self):
378+
co = compile(dedent("""async def ticker():
379+
for i in range(10):
380+
yield i
381+
await asyncio.sleep(0)"""), '?', 'exec', flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
382+
glob = {}
383+
exec(co, glob)
384+
385+
from types import AsyncGeneratorType
386+
387+
self.assertEqual(type(glob['ticker']()), AsyncGeneratorType)
388+
389+
361390
def test_delattr(self):
362391
sys.spam = 1
363392
delattr(sys, 'spam')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The ``compile()`` builtin functions now support the ``ast.PyCF_ALLOW_TOP_LEVEL_AWAIT`` flag, which allow to compile sources that contains top-level ``await``, ``async with`` or ``async for``. This is useful to evaluate async-code from with an already async functions; for example in a custom REPL.

Parser/asdl_c.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,8 @@ def visitModule(self, mod):
10001000
self.emit("if (!m) return NULL;", 1)
10011001
self.emit("d = PyModule_GetDict(m);", 1)
10021002
self.emit('if (PyDict_SetItemString(d, "AST", (PyObject*)&AST_type) < 0) return NULL;', 1)
1003+
self.emit('if (PyModule_AddIntMacro(m, PyCF_ALLOW_TOP_LEVEL_AWAIT) < 0)', 1)
1004+
self.emit("return NULL;", 2)
10031005
self.emit('if (PyModule_AddIntMacro(m, PyCF_ONLY_AST) < 0)', 1)
10041006
self.emit("return NULL;", 2)
10051007
self.emit('if (PyModule_AddIntMacro(m, PyCF_TYPE_COMMENTS) < 0)', 1)

Python/Python-ast.c

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/compile.c

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2608,10 +2608,13 @@ static int
26082608
compiler_async_for(struct compiler *c, stmt_ty s)
26092609
{
26102610
basicblock *start, *except, *end;
2611-
if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION) {
2611+
if (c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT){
2612+
c->u->u_ste->ste_coroutine = 1;
2613+
} else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION) {
26122614
return compiler_error(c, "'async for' outside async function");
26132615
}
26142616

2617+
26152618
start = compiler_new_block(c);
26162619
except = compiler_new_block(c);
26172620
end = compiler_new_block(c);
@@ -4550,10 +4553,10 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos)
45504553
withitem_ty item = asdl_seq_GET(s->v.AsyncWith.items, pos);
45514554

45524555
assert(s->kind == AsyncWith_kind);
4553-
if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION) {
4554-
if (!(c->c_flags->cf_flags & 0x2000)) {
4555-
return compiler_error(c, "'async with' outside async function");
4556-
}
4556+
if (c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT){
4557+
c->u->u_ste->ste_coroutine = 1;
4558+
} else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION){
4559+
return compiler_error(c, "'async with' outside async function");
45574560
}
45584561

45594562
block = compiler_new_block(c);
@@ -4761,18 +4764,17 @@ compiler_visit_expr1(struct compiler *c, expr_ty e)
47614764
ADDOP(c, YIELD_FROM);
47624765
break;
47634766
case Await_kind:
4764-
if (c->u->u_ste->ste_type != FunctionBlock) {
4765-
if (!(c->c_flags->cf_flags & 0x2000)) {
4767+
if (c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT){
4768+
c->u->u_ste->ste_coroutine = 1;
4769+
} else {
4770+
if (c->u->u_ste->ste_type != FunctionBlock){
47664771
return compiler_error(c, "'await' outside function");
47674772
}
47684773

4769-
c->u->u_ste->ste_coroutine = 1;
4770-
}
4771-
4772-
if (!(c->c_flags->cf_flags & 0x2000)) {
4773-
if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION &&
4774-
c->u->u_scope_type != COMPILER_SCOPE_COMPREHENSION)
4774+
if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION &&
4775+
c->u->u_scope_type != COMPILER_SCOPE_COMPREHENSION){
47754776
return compiler_error(c, "'await' outside async function");
4777+
}
47764778
}
47774779

47784780
VISIT(c, expr, e->v.Await.value);
@@ -5708,7 +5710,7 @@ compute_code_flags(struct compiler *c)
57085710
/* (Only) inherit compilerflags in PyCF_MASK */
57095711
flags |= (c->c_flags->cf_flags & PyCF_MASK);
57105712

5711-
if ((c->c_flags->cf_flags & 0x2000) && c->u->u_ste->ste_coroutine) {
5713+
if ((c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT) && ste->ste_coroutine && !ste->ste_generator) {
57125714
flags |= CO_COROUTINE;
57135715
}
57145716

0 commit comments

Comments
 (0)