Skip to content

Commit ef0f544

Browse files
authored
fix returning tuples from async fns (#4407)
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.
1 parent b50fd81 commit ef0f544

File tree

3 files changed

+16
-1
lines changed

3 files changed

+16
-1
lines changed

newsfragments/4407.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix async functions returning a tuple only returning the first element to Python.

src/coroutine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ impl Coroutine {
9595
match panic::catch_unwind(panic::AssertUnwindSafe(poll)) {
9696
Ok(Poll::Ready(res)) => {
9797
self.close();
98-
return Err(PyStopIteration::new_err(res?));
98+
return Err(PyStopIteration::new_err((res?,)));
9999
}
100100
Err(err) => {
101101
self.close();

tests/test_coroutine.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ fn sleep_coroutine() {
121121
})
122122
}
123123

124+
#[pyfunction]
125+
async fn return_tuple() -> (usize, usize) {
126+
(42, 43)
127+
}
128+
129+
#[test]
130+
fn tuple_coroutine() {
131+
Python::with_gil(|gil| {
132+
let func = wrap_pyfunction!(return_tuple, gil).unwrap();
133+
let test = r#"import asyncio; assert asyncio.run(func()) == (42, 43)"#;
134+
py_run!(gil, func, &handle_windows(test));
135+
})
136+
}
137+
124138
#[test]
125139
fn cancelled_coroutine() {
126140
Python::with_gil(|gil| {

0 commit comments

Comments
 (0)