Skip to content

Commit d308c0b

Browse files
authored
[mypyc] Fix generation of function wrappers for decorated functions (#20584)
For decorated functions, a `FuncDecl` is constructed during compilation from another `FuncDecl` but some attributes are not copied. One of those is `is_coroutine` which is used to determine whether the function will be replaced in the module dictionary by a `CPyFunction` object. The `CPyFunction` object is needed for introspection functions to work so without it, `inspect.iscoroutinefunction(fn)` returns `False` for decorated async functions. This causes incorrect behavior when the `inspect` function is used in a decorator, for example: ```python F = TypeVar("F", bound=Callable[..., Any]) def wrap(fn: F) -> F: if inspect.iscoroutinefunction(fn): @wraps(fn) async def wrapper_async(*args) -> Any: return await fn(*args) + await fn(*args) return cast(F, wrapper_async) @wraps(fn) def wrapper(*args) -> Any: return fn(*args) + fn(*args) return cast(F, wrapper) @Wrap async def wrapped_async(val: int) -> int: return val ``` In existing test cases this did not come up because the decorators would unconditionally wrap an async function with another async function. Checking `iscoroutinefunction` of the wrapped function would check the wrapper instead, for which a `CPyFunction` object was generated correctly. But during execution of `wrap`, the actual wrapped function is checked.
1 parent 131f9d9 commit d308c0b

File tree

2 files changed

+15
-11
lines changed

2 files changed

+15
-11
lines changed

mypyc/irbuild/function.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ def gen_func_ir(
365365
func_decl.kind,
366366
is_prop_getter=func_decl.is_prop_getter,
367367
is_prop_setter=func_decl.is_prop_setter,
368+
is_generator=func_decl.is_generator,
369+
is_coroutine=func_decl.is_coroutine,
370+
implicit=func_decl.implicit,
371+
internal=func_decl.internal,
368372
)
369373
func_ir = FuncIR(func_decl, args, blocks, fitem.line, traceback_name=fitem.name)
370374
else:

mypyc/test-data/run-async.test

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,16 +1432,16 @@ async def identity_async(val: int) -> int:
14321432
F = TypeVar("F", bound=Callable[..., Any])
14331433

14341434
def wrap(fn: F) -> F:
1435-
@wraps(fn)
1436-
def wrapper(*args) -> Any:
1437-
return fn(*args) + fn(*args)
1435+
if is_coroutine(fn):
1436+
@wraps(fn)
1437+
async def wrapper_async(*args) -> Any:
1438+
return await fn(*args) + await fn(*args)
14381439

1439-
return cast(F, wrapper)
1440+
return cast(F, wrapper_async)
14401441

1441-
def wrap_async(fn: F) -> F:
14421442
@wraps(fn)
1443-
async def wrapper(*args) -> Any:
1444-
return await fn(*args) + await fn(*args)
1443+
def wrapper(*args) -> Any:
1444+
return fn(*args) + fn(*args)
14451445

14461446
return cast(F, wrapper)
14471447

@@ -1453,11 +1453,11 @@ def wrapped(val: int) -> int:
14531453
def wrapped2(val: int) -> int:
14541454
return val * 2
14551455

1456-
@wrap_async
1456+
@wrap
14571457
async def wrapped_async(val: int) -> int:
14581458
return val
14591459

1460-
@wrap_async
1460+
@wrap
14611461
async def wrapped2_async(val: int) -> int:
14621462
return val * 2
14631463

@@ -1472,7 +1472,7 @@ class T:
14721472
def returns_two(self) -> int:
14731473
return 1
14741474

1475-
@wrap_async
1475+
@wrap
14761476
async def returns_two_async(self) -> int:
14771477
return 1
14781478

@@ -1569,7 +1569,7 @@ def test_nested() -> None:
15691569
def nested_wrapped() -> int:
15701570
return 2
15711571

1572-
@wrap_async
1572+
@wrap
15731573
async def nested_wrapped_async() -> int:
15741574
return 2
15751575

0 commit comments

Comments
 (0)