Skip to content

Commit bb046f9

Browse files
authored
Merge pull request #3540 from WarriorOfWire/async_syntax
__await__ magic method and async/await
2 parents 2e5bea2 + 10badd9 commit bb046f9

File tree

15 files changed

+104
-51
lines changed

15 files changed

+104
-51
lines changed

locale/circuitpython.pot

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgid ""
88
msgstr ""
99
"Project-Id-Version: PACKAGE VERSION\n"
1010
"Report-Msgid-Bugs-To: \n"
11-
"POT-Creation-Date: 2020-10-06 13:26-0400\n"
11+
"POT-Creation-Date: 2020-10-10 23:49-0700\n"
1212
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414
"Language-Team: LANGUAGE <[email protected]>\n"
@@ -246,6 +246,10 @@ msgstr ""
246246
msgid "'return' outside function"
247247
msgstr ""
248248

249+
#: py/compile.c
250+
msgid "'yield from' inside async function"
251+
msgstr ""
252+
249253
#: py/compile.c
250254
msgid "'yield' outside function"
251255
msgstr ""
@@ -289,6 +293,7 @@ msgid "All I2C peripherals are in use"
289293
msgstr ""
290294

291295
#: ports/atmel-samd/common-hal/canio/Listener.c
296+
#: ports/stm/common-hal/canio/Listener.c
292297
msgid "All RX FIFOs in use"
293298
msgstr ""
294299

@@ -896,6 +901,7 @@ msgid "File exists"
896901
msgstr ""
897902

898903
#: ports/atmel-samd/common-hal/canio/Listener.c
904+
#: ports/stm/common-hal/canio/Listener.c
899905
msgid "Filters too complex"
900906
msgstr ""
901907

@@ -932,7 +938,8 @@ msgid "Group full"
932938
msgstr ""
933939

934940
#: ports/mimxrt10xx/common-hal/busio/SPI.c ports/stm/common-hal/busio/I2C.c
935-
#: ports/stm/common-hal/busio/SPI.c ports/stm/common-hal/sdioio/SDCard.c
941+
#: ports/stm/common-hal/busio/SPI.c ports/stm/common-hal/canio/CAN.c
942+
#: ports/stm/common-hal/sdioio/SDCard.c
936943
msgid "Hardware busy, try alternative pins"
937944
msgstr ""
938945

@@ -1002,7 +1009,8 @@ msgid "Invalid %q pin"
10021009
msgstr ""
10031010

10041011
#: ports/stm/common-hal/busio/I2C.c ports/stm/common-hal/busio/SPI.c
1005-
#: ports/stm/common-hal/busio/UART.c ports/stm/common-hal/sdioio/SDCard.c
1012+
#: ports/stm/common-hal/busio/UART.c ports/stm/common-hal/canio/CAN.c
1013+
#: ports/stm/common-hal/sdioio/SDCard.c
10061014
msgid "Invalid %q pin selection"
10071015
msgstr ""
10081016

@@ -3426,6 +3434,10 @@ msgstr ""
34263434
msgid "type object '%q' has no attribute '%q'"
34273435
msgstr ""
34283436

3437+
#: py/objgenerator.c
3438+
msgid "type object 'generator' has no attribute '__await__'"
3439+
msgstr ""
3440+
34293441
#: py/objtype.c
34303442
msgid "type takes 1 or 3 arguments"
34313443
msgstr ""

ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ CIRCUITPY_COUNTIO = 0
1616
CIRCUITPY_FREQUENCYIO = 0
1717
CIRCUITPY_I2CPERIPHERAL = 0
1818
CIRCUITPY_VECTORIO = 0
19+
MICROPY_PY_ASYNC_AWAIT = 0
1920

2021
SUPEROPT_GC = 0
2122

ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ LONGINT_IMPL = MPZ
1515
CIRCUITPY_DISPLAYIO = 0
1616
CIRCUITPY_FREQUENCYIO = 0
1717
CIRCUITPY_I2CPERIPHERAL = 0
18+
MICROPY_PY_ASYNC_AWAIT = 0
1819

1920
SUPEROPT_GC = 0
2021
CFLAGS_INLINE_LIMIT = 55

ports/atmel-samd/boards/feather_m0_rfm69/mpconfigboard.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ CIRCUITPY_USB_MIDI = 0
2222
CIRCUITPY_USB_HID = 0
2323
CIRCUITPY_TOUCHIO = 0
2424
CFLAGS_INLINE_LIMIT = 35
25+
2526
# Make more room.
2627
SUPEROPT_GC = 0
2728

ports/atmel-samd/boards/feather_m0_rfm9x/mpconfigboard.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ CIRCUITPY_SAMD = 0
2222
CIRCUITPY_USB_MIDI = 0
2323
CIRCUITPY_USB_HID = 0
2424
CIRCUITPY_TOUCHIO = 0
25+
2526
CFLAGS_INLINE_LIMIT = 35
2627
# Make more room.
2728
SUPEROPT_GC = 0

ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ CIRCUITPY_COUNTIO = 0
1717
CIRCUITPY_RTC = 0
1818
CIRCUITPY_FREQUENCYIO = 0
1919
CIRCUITPY_I2CPERIPHERAL = 0
20+
MICROPY_PY_ASYNC_AWAIT = 0
2021

2122
SUPEROPT_GC = 0
2223

ports/nrf/boards/pca10100/mpconfigboard.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ CIRCUITPY_RTC = 1
2424
CIRCUITPY_SDCARDIO = 0
2525
CIRCUITPY_TOUCHIO = 0
2626
CIRCUITPY_ULAB = 0
27+
MICROPY_PY_ASYNC_AWAIT = 0
2728

2829
SUPEROPT_GC = 0
2930

ports/nrf/boards/sparkfun_nrf52840_mini/mpconfigboard.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ USB_MANUFACTURER = "SparkFun Electronics"
66
MCU_CHIP = nrf52840
77

88
INTERNAL_FLASH_FILESYSTEM = 1
9+
10+
MICROPY_PY_ASYNC_AWAIT = 0

py/circuitpy_mpconfig.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@
7979

8080
#define MICROPY_PY_ARRAY (1)
8181
#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1)
82-
#define MICROPY_PY_ASYNC_AWAIT (0)
8382
#define MICROPY_PY_ATTRTUPLE (1)
8483

8584
#define MICROPY_PY_BUILTINS_BYTEARRAY (1)

py/circuitpy_mpconfig.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
CIRCUITPY_FULL_BUILD ?= 1
3333
CFLAGS += -DCIRCUITPY_FULL_BUILD=$(CIRCUITPY_FULL_BUILD)
3434

35+
# async/await language keyword support
36+
MICROPY_PY_ASYNC_AWAIT ?= $(CIRCUITPY_FULL_BUILD)
37+
CFLAGS += -DMICROPY_PY_ASYNC_AWAIT=$(MICROPY_PY_ASYNC_AWAIT)
3538

3639
CIRCUITPY_AESIO ?= 0
3740
CFLAGS += -DCIRCUITPY_AESIO=$(CIRCUITPY_AESIO)

py/compile.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,7 @@ STATIC void compile_decorated(compiler_t *comp, mp_parse_node_struct_t *pns) {
853853
mp_parse_node_struct_t *pns0 = (mp_parse_node_struct_t*)pns_body->nodes[0];
854854
body_name = compile_funcdef_helper(comp, pns0, emit_options);
855855
scope_t *fscope = (scope_t*)pns0->nodes[4];
856-
fscope->scope_flags |= MP_SCOPE_FLAG_GENERATOR;
856+
fscope->scope_flags |= MP_SCOPE_FLAG_GENERATOR | MP_SCOPE_FLAG_ASYNC;
857857
#endif
858858
} else {
859859
assert(MP_PARSE_NODE_STRUCT_KIND(pns_body) == PN_classdef); // should be
@@ -2632,6 +2632,12 @@ STATIC void compile_yield_expr(compiler_t *comp, mp_parse_node_struct_t *pns) {
26322632
EMIT_ARG(yield, MP_EMIT_YIELD_VALUE);
26332633
} else if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_yield_arg_from)) {
26342634
pns = (mp_parse_node_struct_t*)pns->nodes[0];
2635+
#if MICROPY_PY_ASYNC_AWAIT
2636+
if(comp->scope_cur->scope_flags & MP_SCOPE_FLAG_ASYNC) {
2637+
compile_syntax_error(comp, (mp_parse_node_t)pns, translate("'yield from' inside async function"));
2638+
return;
2639+
}
2640+
#endif
26352641
compile_node(comp, pns->nodes[0]);
26362642
compile_yield_from(comp);
26372643
} else {
@@ -2648,7 +2654,14 @@ STATIC void compile_atom_expr_await(compiler_t *comp, mp_parse_node_struct_t *pn
26482654
}
26492655
compile_require_async_context(comp, pns);
26502656
compile_atom_expr_normal(comp, pns);
2651-
compile_yield_from(comp);
2657+
2658+
// If it's an awaitable thing, need to reach for the __await__ method for the coroutine.
2659+
// async def functions' __await__ return themselves, which are able to receive a send(),
2660+
// while other types with custom __await__ implementations return async generators.
2661+
EMIT_ARG(load_method, MP_QSTR___await__, false);
2662+
EMIT_ARG(call_method, 0, 0, 0);
2663+
EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE);
2664+
EMIT_ARG(yield, MP_EMIT_YIELD_FROM);
26522665
}
26532666
#endif
26542667

py/objgenerator.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,21 @@ STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) {
225225

226226
STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send);
227227

228+
#if MICROPY_PY_ASYNC_AWAIT
229+
STATIC mp_obj_t gen_instance_await(mp_obj_t self_in) {
230+
mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in);
231+
if ( !self->coroutine_generator ) {
232+
// Pretend like a generator does not have this coroutine behavior.
233+
// Pay no attention to the dir() behind the curtain
234+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_AttributeError,
235+
translate("type object 'generator' has no attribute '__await__'")));
236+
}
237+
// You can directly call send on a coroutine generator or you can __await__ then send on the return of that.
238+
return self;
239+
}
240+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_await_obj, gen_instance_await);
241+
#endif
242+
228243
STATIC mp_obj_t gen_instance_close(mp_obj_t self_in);
229244
STATIC mp_obj_t gen_instance_throw(size_t n_args, const mp_obj_t *args) {
230245
mp_obj_t exc = (n_args == 2) ? args[1] : args[2];
@@ -280,6 +295,9 @@ STATIC const mp_rom_map_elem_t gen_instance_locals_dict_table[] = {
280295
#if MICROPY_PY_GENERATOR_PEND_THROW
281296
{ MP_ROM_QSTR(MP_QSTR_pend_throw), MP_ROM_PTR(&gen_instance_pend_throw_obj) },
282297
#endif
298+
#if MICROPY_PY_ASYNC_AWAIT
299+
{ MP_ROM_QSTR(MP_QSTR___await__), MP_ROM_PTR(&gen_instance_await_obj) },
300+
#endif
283301
};
284302

285303
STATIC MP_DEFINE_CONST_DICT(gen_instance_locals_dict, gen_instance_locals_dict_table);

tests/basics/async_await2.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
# test await expression
22

3-
import sys
4-
if sys.implementation.name in ('micropython', 'circuitpython'):
5-
# uPy allows normal generators to be awaitables
6-
coroutine = lambda f: f
7-
else:
8-
import types
9-
coroutine = types.coroutine
3+
# uPy allows normal generators to be awaitables.
4+
# CircuitPython does not.
5+
# In CircuitPython you need to have an __await__ method on an awaitable like in CPython;
6+
# and like in CPython, generators do not have __await__.
107

11-
@coroutine
12-
def wait(value):
13-
print('wait value:', value)
14-
msg = yield 'message from wait(%u)' % value
15-
print('wait got back:', msg)
16-
return 10
8+
class Awaitable:
9+
def __init__(self, value):
10+
self.value = value
11+
12+
def __await__(self):
13+
print('wait value:', self.value)
14+
msg = yield 'message from wait(%u)' % self.value
15+
print('wait got back:', msg)
16+
return 10
1717

1818
async def f():
19-
x = await wait(1)**2
19+
x = await Awaitable(1)**2
2020
print('x =', x)
2121

2222
coro = f()

tests/basics/async_for2.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# test waiting within "async for" __anext__ function
22

3-
import sys
4-
if sys.implementation.name in ('micropython', 'circuitpython'):
5-
# uPy allows normal generators to be awaitables
6-
coroutine = lambda f: f
7-
else:
8-
import types
9-
coroutine = types.coroutine
10-
11-
@coroutine
12-
def f(x):
13-
print('f start:', x)
14-
yield x + 1
15-
yield x + 2
16-
return x + 3
3+
# uPy allows normal generators to be awaitables.
4+
# CircuitPython does not.
5+
# In CircuitPython you need to have an __await__ method on an awaitable like in CPython;
6+
# and like in CPython, generators do not have __await__.
7+
8+
class Awaitable:
9+
def __init__(self, x):
10+
self.x = x
11+
12+
def __await__(self):
13+
print('f start:', self.x)
14+
yield self.x + 1
15+
yield self.x + 2
16+
return self.x + 3
1717

1818
class ARange:
1919
def __init__(self, high):
@@ -27,7 +27,7 @@ def __aiter__(self):
2727

2828
async def __anext__(self):
2929
print('anext')
30-
print('f returned:', await f(20))
30+
print('f returned:', await Awaitable(20))
3131
if self.cur < self.high:
3232
val = self.cur
3333
self.cur += 1

tests/basics/async_with2.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
# test waiting within async with enter/exit functions
22

3-
import sys
4-
if sys.implementation.name in ('micropython', 'circuitpython'):
5-
# uPy allows normal generators to be awaitables
6-
coroutine = lambda f: f
7-
else:
8-
import types
9-
coroutine = types.coroutine
3+
# uPy allows normal generators to be awaitables.
4+
# CircuitPython does not.
5+
# In CircuitPython you need to have an __await__ method on an awaitable like in CPython;
6+
# and like in CPython, generators do not have __await__.
107

11-
@coroutine
12-
def f(x):
13-
print('f start:', x)
14-
yield x + 1
15-
yield x + 2
16-
return x + 3
8+
class Awaitable:
9+
def __init__(self, x):
10+
self.x = x
11+
12+
def __await__(self):
13+
print('f start:', self.x)
14+
yield self.x + 1
15+
yield self.x + 2
16+
return self.x + 3
1717

1818
class AContext:
1919
async def __aenter__(self):
2020
print('enter')
21-
print('f returned:', await f(10))
21+
print('f returned:', await Awaitable(10))
2222
async def __aexit__(self, exc_type, exc, tb):
2323
print('exit', exc_type, exc)
24-
print('f returned:', await f(20))
24+
print('f returned:', await Awaitable(20))
2525

2626
async def coro():
2727
async with AContext():
2828
print('body start')
29-
print('body f returned:', await f(30))
29+
print('body f returned:', await Awaitable(30))
3030
print('body end')
3131

3232
o = coro()

0 commit comments

Comments
 (0)