Repository: smarie/python-makefun

Issue:
`SyntaxError` when creating a native coroutine function with name containing 'return'
```python
from makefun import create_function

async def my_native_coroutine_handler(sleep_time):
    await sleep(sleep_time)
    return sleep_time

create_function("foo_returns_bar(sleep_time=2)", my_native_coroutine_handler)
```

raises

```
        filename = '<makefun-gen-%d>' % (next(_compile_count),)
        try:
>           code = compile(body, filename, 'single')
E             File "<makefun-gen-11>", line 1
E               async def foo_return awaits_bar(sleep_time=2):
E                                    ^
E           SyntaxError: invalid syntax

..\src\makefun\main.py:714: SyntaxError
```

Discussion:

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 159307b..d870b3e 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,5 +1,10 @@
 # Changelog
 
+### 1.15.2 - bugfix
+
+ - Fixed `SyntaxError` happening when the name of a native coroutine function to create contains `'return'`.
+   Fixes [#96](https://github.com/smarie/python-makefun/issues/96).
+
 ### 1.15.1 - bugfixes
 
  - Fixed `ValueError: Invalid co_name` happening on python 2 when the name of a function to create starts or ends with
diff --git a/src/makefun/main.py b/src/makefun/main.py
index ffa389b..1e763e7 100644
--- a/src/makefun/main.py
+++ b/src/makefun/main.py
@@ -312,7 +312,7 @@ def create_function(func_signature,             # type: Union[str, Signature]
         body = "def %s\n    return _func_impl_(%s)\n" % (func_signature_str, params_str)
 
     if iscoroutinefunction(func_impl):
-        body = ("async " + body).replace('return', 'return await')
+        body = ("async " + body).replace('return _func_impl_', 'return await _func_impl_')
 
     # create the function by compiling code, mapping the `_func_impl_` symbol to `func_impl`
     protect_eval_dict(evaldict, func_name, params_names)


Test patch:
diff --git a/tests/test_generators_coroutines.py b/tests/test_generators_coroutines.py
index 7a9f13a..f9ec779 100644
--- a/tests/test_generators_coroutines.py
+++ b/tests/test_generators_coroutines.py
@@ -95,3 +95,25 @@ def test_native_coroutine():
     from asyncio import get_event_loop
     out = get_event_loop().run_until_complete(dynamic_fun(0.1))
     assert out == 0.1
+
+
+@pytest.mark.skipif(sys.version_info < (3, 5), reason="native coroutines with async/await require python3.6 or higher")
+def test_issue_96():
+    """Same as `test_native_coroutine` but tests that we can use 'return' in the coroutine name"""
+
+    # define the handler that should be called
+    from tests._test_py35 import make_native_coroutine_handler
+    my_native_coroutine_handler = make_native_coroutine_handler()
+
+    # create the dynamic function
+    dynamic_fun = create_function("foo_returns_bar(sleep_time=2)", my_native_coroutine_handler)
+
+    # check that this is a coroutine for inspect and for asyncio
+    assert iscoroutinefunction(dynamic_fun)
+    from asyncio import iscoroutinefunction as is_native_co
+    assert is_native_co(dynamic_fun)
+
+    # verify that the new function is a native coroutine and behaves correctly
+    from asyncio import get_event_loop
+    out = get_event_loop().run_until_complete(dynamic_fun(0.1))
+    assert out == 0.1