`FunctionMaker.create` raises unexpected SyntaxError when return is present as substring for async function
pengzhengyi opened this issue · 0 comments
pengzhengyi commented
About
In current FunctionMaker.create
, if the function to create from is an async function and if the function body contains any occurrence of return, it will blindly replace it with return await
. This will raise a SyntaxError in edge cases where return
is present not as the keyword return but a substring.
Reproduce
from decorator import FunctionMaker
async def IDENTITY(return_value):
return return_value
FunctionMaker.create('WILL_RAISE_SYNTAX_ERROR()', 'return IDENTITY(return_value=actual_return_value)', dict(IDENTITY=IDENTITY, _call_=IDENTITY, actual_return_value=10), addsource=True)
Error Message
Error in generated code:
async def WILL_RAISE_SYNTAX_ERROR():
return await IDENTITY(return await_value=actual_return await_value)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/opt/conda/lib/python3.9/site-packages/decorator.py", line 196, in create
return self.make(body, evaldict, addsource, **attrs)
File "/opt/conda/lib/python3.9/site-packages/decorator.py", line 159, in make
code = compile(src, filename, 'single')
File "<decorator-gen-0>", line 2
return await IDENTITY(return await_value=actual_return await_value)
^
SyntaxError: invalid syntax
Analysis
FunctionMaker.create
has the following:
if caller and iscoroutinefunction(caller):
body = ('async def %(name)s(%(signature)s):\n' + ibody).replace(
'return', 'return await')
However, this replacement is blind, if a parameter is named like return_value
, it will be also replaced as it contains return
(like the above example).
Moreover, the following lines can produce similar errors:
return_value = ...
A simple assignment statement"This is a long message containing a return word"
Fix
To fix, we need to match return as a keyword rather than as any substring. A straightforward fix looks like the following (not fully tested):
if caller and iscoroutinefunction(caller):
rawbody = "async def %(name)s(%(signature)s):\n" + ibody
body = re.sub(r"(^|\s)return(\s)", r"\1return await\2", rawbody)