Commit 65bbb6f2 authored by Pedro Marques da Luz's avatar Pedro Marques da Luz Committed by GitHub

Make asyncio.iscoroutinefunction() recognise Cython compiled coroutines. (GH-3427)

Python's asyncio.coroutines uses an object to tag objects as coroutine functions. We now read this object and use it to tag Cython compiled coroutines as well.

It also includes tests to make sure `asyncio.iscoroutinefunction()` works as expected.
This doesn't fix `inspect.iscouroutinefunction()` (which uses a flag that can trigger undesirable behaviour for cython functions).

Closes https://github.com/cython/cython/issues/2273
parent ae67505e
...@@ -9486,6 +9486,9 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -9486,6 +9486,9 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
if def_node.local_scope.parent_scope.is_c_class_scope and not def_node.entry.is_anonymous: if def_node.local_scope.parent_scope.is_c_class_scope and not def_node.entry.is_anonymous:
flags.append('__Pyx_CYFUNCTION_CCLASS') flags.append('__Pyx_CYFUNCTION_CCLASS')
if def_node.is_coroutine:
flags.append('__Pyx_CYFUNCTION_COROUTINE')
if flags: if flags:
flags = ' | '.join(flags) flags = ' | '.join(flags)
else: else:
......
...@@ -1691,6 +1691,8 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -1691,6 +1691,8 @@ class FuncDefNode(StatNode, BlockNode):
needs_outer_scope = False needs_outer_scope = False
pymethdef_required = False pymethdef_required = False
is_generator = False is_generator = False
is_coroutine = False
is_asyncgen = False
is_generator_body = False is_generator_body = False
is_async_def = False is_async_def = False
modifiers = [] modifiers = []
...@@ -4336,9 +4338,7 @@ class GeneratorDefNode(DefNode): ...@@ -4336,9 +4338,7 @@ class GeneratorDefNode(DefNode):
# #
is_generator = True is_generator = True
is_coroutine = False
is_iterable_coroutine = False is_iterable_coroutine = False
is_asyncgen = False
gen_type_name = 'Generator' gen_type_name = 'Generator'
needs_closure = True needs_closure = True
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define __Pyx_CYFUNCTION_STATICMETHOD 0x01 #define __Pyx_CYFUNCTION_STATICMETHOD 0x01
#define __Pyx_CYFUNCTION_CLASSMETHOD 0x02 #define __Pyx_CYFUNCTION_CLASSMETHOD 0x02
#define __Pyx_CYFUNCTION_CCLASS 0x04 #define __Pyx_CYFUNCTION_CCLASS 0x04
#define __Pyx_CYFUNCTION_COROUTINE 0x08
#define __Pyx_CyFunction_GetClosure(f) \ #define __Pyx_CyFunction_GetClosure(f) \
(((__pyx_CyFunctionObject *) (f))->func_closure) (((__pyx_CyFunctionObject *) (f))->func_closure)
...@@ -47,6 +48,9 @@ typedef struct { ...@@ -47,6 +48,9 @@ typedef struct {
PyObject *defaults_kwdict; /* Const kwonly defaults dict */ PyObject *defaults_kwdict; /* Const kwonly defaults dict */
PyObject *(*defaults_getter)(PyObject *); PyObject *(*defaults_getter)(PyObject *);
PyObject *func_annotations; /* function annotations dict */ PyObject *func_annotations; /* function annotations dict */
// Coroutine marker
PyObject *func_is_coroutine;
} __pyx_CyFunctionObject; } __pyx_CyFunctionObject;
#if !CYTHON_COMPILING_IN_LIMITED_API #if !CYTHON_COMPILING_IN_LIMITED_API
...@@ -92,6 +96,7 @@ static PyObject * __Pyx_CyFunction_Vectorcall_FASTCALL_KEYWORDS(PyObject *func, ...@@ -92,6 +96,7 @@ static PyObject * __Pyx_CyFunction_Vectorcall_FASTCALL_KEYWORDS(PyObject *func,
//@requires: CommonStructures.c::FetchCommonType //@requires: CommonStructures.c::FetchCommonType
//@requires: ObjectHandling.c::PyMethodNew //@requires: ObjectHandling.c::PyMethodNew
//@requires: ObjectHandling.c::PyVectorcallFastCallDict //@requires: ObjectHandling.c::PyVectorcallFastCallDict
//@requires: ObjectHandling.c::PyObjectGetAttrStr
#include <structmember.h> #include <structmember.h>
...@@ -362,6 +367,35 @@ __Pyx_CyFunction_get_annotations(__pyx_CyFunctionObject *op, CYTHON_UNUSED void ...@@ -362,6 +367,35 @@ __Pyx_CyFunction_get_annotations(__pyx_CyFunctionObject *op, CYTHON_UNUSED void
return result; return result;
} }
static PyObject *
__Pyx_CyFunction_get_is_coroutine(__pyx_CyFunctionObject *op, CYTHON_UNUSED void *context) {
if (op->func_is_coroutine)
return __Pyx_NewRef(op->func_is_coroutine);
int is_coroutine = op->flags & __Pyx_CYFUNCTION_COROUTINE;
#if PY_VERSION_HEX >= 0x03050000
if (is_coroutine) {
PyObject *module, *fromlist, *marker = PYIDENT("_is_coroutine");
fromlist = PyList_New(1);
if (unlikely(!fromlist)) return NULL;
PyList_SET_ITEM(fromlist, 0, marker);
module = PyImport_ImportModuleLevelObject(PYIDENT("asyncio.coroutines"), NULL, NULL, fromlist, 0);
Py_DECREF(fromlist);
if (unlikely(!module)) goto ignore;
op->func_is_coroutine = __Pyx_PyObject_GetAttrStr(module, marker);
Py_DECREF(module);
if (unlikely(!op->func_is_coroutine)) goto ignore;
return __Pyx_NewRef(op->func_is_coroutine);
}
ignore:
PyErr_Clear();
#endif
op->func_is_coroutine = __Pyx_PyBool_FromLong(is_coroutine);
return __Pyx_NewRef(op->func_is_coroutine);
}
//#if PY_VERSION_HEX >= 0x030400C1 //#if PY_VERSION_HEX >= 0x030400C1
//static PyObject * //static PyObject *
//__Pyx_CyFunction_get_signature(__pyx_CyFunctionObject *op, CYTHON_UNUSED void *context) { //__Pyx_CyFunction_get_signature(__pyx_CyFunctionObject *op, CYTHON_UNUSED void *context) {
...@@ -406,6 +440,7 @@ static PyGetSetDef __pyx_CyFunction_getsets[] = { ...@@ -406,6 +440,7 @@ static PyGetSetDef __pyx_CyFunction_getsets[] = {
{(char *) "__defaults__", (getter)__Pyx_CyFunction_get_defaults, (setter)__Pyx_CyFunction_set_defaults, 0, 0}, {(char *) "__defaults__", (getter)__Pyx_CyFunction_get_defaults, (setter)__Pyx_CyFunction_set_defaults, 0, 0},
{(char *) "__kwdefaults__", (getter)__Pyx_CyFunction_get_kwdefaults, (setter)__Pyx_CyFunction_set_kwdefaults, 0, 0}, {(char *) "__kwdefaults__", (getter)__Pyx_CyFunction_get_kwdefaults, (setter)__Pyx_CyFunction_set_kwdefaults, 0, 0},
{(char *) "__annotations__", (getter)__Pyx_CyFunction_get_annotations, (setter)__Pyx_CyFunction_set_annotations, 0, 0}, {(char *) "__annotations__", (getter)__Pyx_CyFunction_get_annotations, (setter)__Pyx_CyFunction_set_annotations, 0, 0},
{(char *) "_is_coroutine", (getter)__Pyx_CyFunction_get_is_coroutine, 0, 0, 0},
//#if PY_VERSION_HEX >= 0x030400C1 //#if PY_VERSION_HEX >= 0x030400C1
// {(char *) "__signature__", (getter)__Pyx_CyFunction_get_signature, 0, 0, 0}, // {(char *) "__signature__", (getter)__Pyx_CyFunction_get_signature, 0, 0, 0},
//#endif //#endif
...@@ -478,6 +513,7 @@ static PyObject *__Pyx_CyFunction_Init(__pyx_CyFunctionObject *op, PyMethodDef * ...@@ -478,6 +513,7 @@ static PyObject *__Pyx_CyFunction_Init(__pyx_CyFunctionObject *op, PyMethodDef *
op->defaults_kwdict = NULL; op->defaults_kwdict = NULL;
op->defaults_getter = NULL; op->defaults_getter = NULL;
op->func_annotations = NULL; op->func_annotations = NULL;
op->func_is_coroutine = NULL;
#if CYTHON_METH_FASTCALL #if CYTHON_METH_FASTCALL
switch (ml->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS)) { switch (ml->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS)) {
case METH_NOARGS: case METH_NOARGS:
...@@ -518,6 +554,7 @@ __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m) ...@@ -518,6 +554,7 @@ __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m)
Py_CLEAR(m->defaults_tuple); Py_CLEAR(m->defaults_tuple);
Py_CLEAR(m->defaults_kwdict); Py_CLEAR(m->defaults_kwdict);
Py_CLEAR(m->func_annotations); Py_CLEAR(m->func_annotations);
Py_CLEAR(m->func_is_coroutine);
if (m->defaults) { if (m->defaults) {
PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m); PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m);
...@@ -560,6 +597,7 @@ static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit, ...@@ -560,6 +597,7 @@ static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit,
Py_VISIT(m->func_classobj); Py_VISIT(m->func_classobj);
Py_VISIT(m->defaults_tuple); Py_VISIT(m->defaults_tuple);
Py_VISIT(m->defaults_kwdict); Py_VISIT(m->defaults_kwdict);
Py_VISIT(m->func_is_coroutine);
if (m->defaults) { if (m->defaults) {
PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m); PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m);
......
# mode: run # mode: run
# tag: asyncio, gh1685 # tag: asyncio, gh1685, gh2273
PYTHON setup.py build_ext -i PYTHON setup.py build_ext -i
PYTHON main.py PYTHON main.py
...@@ -19,6 +19,7 @@ setup( ...@@ -19,6 +19,7 @@ setup(
import asyncio import asyncio
import cy_test import cy_test
import py_test
from contextlib import closing from contextlib import closing
async def main(): async def main():
...@@ -31,6 +32,12 @@ with closing(asyncio.get_event_loop()) as loop: ...@@ -31,6 +32,12 @@ with closing(asyncio.get_event_loop()) as loop:
print("Running Cython coroutine ...") print("Running Cython coroutine ...")
loop.run_until_complete(cy_test.say()) loop.run_until_complete(cy_test.say())
assert asyncio.iscoroutinefunction(cy_test.cy_async_def_example) == True
assert asyncio.iscoroutinefunction(cy_test.cy_async_def_example) == True
assert asyncio.iscoroutinefunction(py_test.py_async_def_example) == True
assert asyncio.iscoroutinefunction(py_test.py_async_def_example) == True
assert asyncio.iscoroutinefunction(cy_test.cy_def_example) == False
assert asyncio.iscoroutinefunction(py_test.py_def_example) == False
######## cy_test.pyx ######## ######## cy_test.pyx ########
...@@ -51,8 +58,19 @@ async def cb(): ...@@ -51,8 +58,19 @@ async def cb():
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
print("done!") print("done!")
async def cy_async_def_example():
return 1
def cy_def_example():
return 1
######## py_test.py ######## ######## py_test.py ########
async def py_async(): async def py_async():
print("- and this one is from Python") print("- and this one is from Python")
async def py_async_def_example():
return 1
def py_def_example():
return 1
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment