async def syntax rigor and __await__ magic method

Some examples of improved compliance with CPython that currently
have divergent behavior in CircuitPython are listed below:

* yield from is not allowed in async methods
```
>>> async def f():
...     yield from 'abc'
...
Traceback (most recent call last):
  File "<stdin>", line 2, in f
SyntaxError: 'yield from' inside async function
```

* await only works on awaitable expressions
```
>>> async def f():
...     await 'not awaitable'
...
>>> f().send(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
AttributeError: 'str' object has no attribute '__await__'
```

* only __await__()able expressions are awaitable
Okay this one actually does not work in circuitpython at all today.
This is how CPython works though and pretending __await__ does not
exist will only bite users who write both.
```
>>> class c:
...     pass
...
>>> def f(self):
...     yield
...     yield
...     return 'f to pay respects'
...
>>> c.__await__ = f  # could just as easily have put it on the class but this shows how it's wired
>>> async def g():
...     awaitable_thing = c()
...     partial = await awaitable_thing
...     return 'press ' + partial
...
>>> q = g()
>>> q.send(None)
>>> q.send(None)
>>> q.send(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: press f to pay respects
```
This commit is contained in:
Kenny 2020-08-05 19:55:40 -07:00
parent 5cadf525bd
commit bf849ff674
2 changed files with 30 additions and 1 deletions

View File

@ -2632,6 +2632,12 @@ STATIC void compile_yield_expr(compiler_t *comp, mp_parse_node_struct_t *pns) {
EMIT_ARG(yield, MP_EMIT_YIELD_VALUE);
} else if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_yield_arg_from)) {
pns = (mp_parse_node_struct_t*)pns->nodes[0];
#if MICROPY_PY_ASYNC_AWAIT
if(comp->scope_cur->scope_flags & MP_SCOPE_FLAG_ASYNC) {
compile_syntax_error(comp, (mp_parse_node_t)pns, translate("'yield from' inside async function"));
return;
}
#endif
compile_node(comp, pns->nodes[0]);
compile_yield_from(comp);
} else {
@ -2648,7 +2654,8 @@ STATIC void compile_atom_expr_await(compiler_t *comp, mp_parse_node_struct_t *pn
}
compile_require_async_context(comp, pns);
compile_atom_expr_normal(comp, pns);
compile_yield_from(comp);
compile_await_object_method(comp, MP_QSTR___await__);
}
#endif

View File

@ -225,6 +225,25 @@ STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) {
STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send);
#if MICROPY_PY_ASYNC_AWAIT
STATIC mp_obj_t gen_instance_await(mp_obj_t self_in) {
mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in);
if ( !self->coroutine_generator ) {
// Pretend like a generator does not have this coroutine behavior.
// Pay no attention to the dir() behind the curtain
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_AttributeError,
translate("type object 'generator' has no attribute '__await__'")));
}
mp_obj_t ret = gen_resume_and_raise(self_in, mp_const_none, MP_OBJ_NULL);
if (ret == MP_OBJ_STOP_ITERATION) {
nlr_raise(mp_obj_new_exception(&mp_type_StopIteration));
} else {
return ret;
}
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_await_obj, gen_instance_await);
#endif
STATIC mp_obj_t gen_instance_close(mp_obj_t self_in);
STATIC mp_obj_t gen_instance_throw(size_t n_args, const mp_obj_t *args) {
mp_obj_t exc = (n_args == 2) ? args[1] : args[2];
@ -280,6 +299,9 @@ STATIC const mp_rom_map_elem_t gen_instance_locals_dict_table[] = {
#if MICROPY_PY_GENERATOR_PEND_THROW
{ MP_ROM_QSTR(MP_QSTR_pend_throw), MP_ROM_PTR(&gen_instance_pend_throw_obj) },
#endif
#if MICROPY_PY_ASYNC_AWAIT
{ MP_ROM_QSTR(MP_QSTR___await__), MP_ROM_PTR(&gen_instance_await_obj) },
#endif
};
STATIC MP_DEFINE_CONST_DICT(gen_instance_locals_dict, gen_instance_locals_dict_table);