Skip to content

Commit

Permalink
fix returning tuples from async fns (#4407)
Browse files Browse the repository at this point in the history
Fixes #4400

As the return value is ultimately communicated back via a StopIteration
exception instance, a peculiar behavior of `PyErr::new` is encountered
here: when the argument is a tuple `arg`, it is used to construct the
exception as if calling from Python `Exception(*arg)` and not
`Exception(arg)` like for every other type of argument.

This comes from from CPython's `PyErr_SetObject` which ultimately calls
`_PyErr_CreateException` where the "culprit" is found here:
https://github.com/python/cpython/blob/main/Python/errors.c#L33

We can fix this particular bug in the invocation of `PyErr::new` but it
is a more general question if we want to keep reflecting this somewhat
surprising CPython behavior, or create a better API, introducing a
breaking change.
  • Loading branch information
birkenfeld authored and davidhewitt committed Sep 3, 2024
1 parent ebd4747 commit d103c76
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 1 deletion.
1 change: 1 addition & 0 deletions newsfragments/4407.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix async functions returning a tuple only returning the first element to Python.
2 changes: 1 addition & 1 deletion src/coroutine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl Coroutine {
match panic::catch_unwind(panic::AssertUnwindSafe(poll)) {
Ok(Poll::Ready(res)) => {
self.close();
return Err(PyStopIteration::new_err(res?));
return Err(PyStopIteration::new_err((res?,)));
}
Err(err) => {
self.close();
Expand Down
14 changes: 14 additions & 0 deletions tests/test_coroutine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ fn sleep_coroutine() {
})
}

#[pyfunction]
async fn return_tuple() -> (usize, usize) {
(42, 43)
}

#[test]
fn tuple_coroutine() {
Python::with_gil(|gil| {
let func = wrap_pyfunction_bound!(return_tuple, gil).unwrap();
let test = r#"import asyncio; assert asyncio.run(func()) == (42, 43)"#;
py_run!(gil, func, &handle_windows(test));
})
}

#[test]
fn cancelled_coroutine() {
Python::with_gil(|gil| {
Expand Down

0 comments on commit d103c76

Please sign in to comment.