Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions Lib/test/test_asyncgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,6 @@ async def check_anext_returning_iterator(self, aiter_class):
await awaitable
return "completed"

# TODO: RUSTPYTHON, NameError: name 'anext' is not defined
@unittest.expectedFailure
def test_anext_return_iterator(self):
class WithIterAnext:
def __aiter__(self):
Expand All @@ -614,8 +612,6 @@ def __anext__(self):
result = self.loop.run_until_complete(self.check_anext_returning_iterator(WithIterAnext))
self.assertEqual(result, "completed")

# TODO: RUSTPYTHON, NameError: name 'anext' is not defined
@unittest.expectedFailure
def test_anext_return_generator(self):
class WithGenAnext:
def __aiter__(self):
Expand All @@ -625,8 +621,6 @@ def __anext__(self):
result = self.loop.run_until_complete(self.check_anext_returning_iterator(WithGenAnext))
self.assertEqual(result, "completed")

# TODO: RUSTPYTHON, NameError: name 'anext' is not defined
@unittest.expectedFailure
def test_anext_await_raises(self):
class RaisingAwaitable:
def __await__(self):
Expand Down
32 changes: 0 additions & 32 deletions Lib/test/test_contextlib_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ async def __aexit__(self, *args):
manager = DefaultAsyncContextManager()
manager.var = 42

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_async_gen_propagates_generator_exit(self):
# A regression test for https://bugs.python.org/issue33786.

Expand Down Expand Up @@ -94,8 +92,6 @@ class NoneAexit(ManagerFromScratch):

class AsyncContextManagerTestCase(unittest.IsolatedAsyncioTestCase):

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_contextmanager_plain(self):
state = []
@asynccontextmanager
Expand All @@ -109,8 +105,6 @@ async def woohoo():
state.append(x)
self.assertEqual(state, [1, 42, 999])

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_contextmanager_finally(self):
state = []
@asynccontextmanager
Expand Down Expand Up @@ -185,8 +179,6 @@ class StopAsyncIterationSubclass(StopAsyncIteration):
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
self.assertEqual(frames[0].line, 'raise stop_exc')

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_contextmanager_no_reraise(self):
@asynccontextmanager
async def whee():
Expand All @@ -196,8 +188,6 @@ async def whee():
# Calling __aexit__ should not result in an exception
self.assertFalse(await ctx.__aexit__(TypeError, TypeError("foo"), None))

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_contextmanager_trap_yield_after_throw(self):
@asynccontextmanager
async def whoo():
Expand All @@ -213,8 +203,6 @@ async def whoo():
# The "gen" attribute is an implementation detail.
self.assertFalse(ctx.gen.ag_suspended)

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_contextmanager_trap_no_yield(self):
@asynccontextmanager
async def whoo():
Expand All @@ -224,8 +212,6 @@ async def whoo():
with self.assertRaises(RuntimeError):
await ctx.__aenter__()

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_contextmanager_trap_second_yield(self):
@asynccontextmanager
async def whoo():
Expand All @@ -239,8 +225,6 @@ async def whoo():
# The "gen" attribute is an implementation detail.
self.assertFalse(ctx.gen.ag_suspended)

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_contextmanager_non_normalised(self):
@asynccontextmanager
async def whoo():
Expand All @@ -254,8 +238,6 @@ async def whoo():
with self.assertRaises(SyntaxError):
await ctx.__aexit__(RuntimeError, None, None)

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_contextmanager_except(self):
state = []
@asynccontextmanager
Expand Down Expand Up @@ -301,8 +283,6 @@ class StopAsyncIterationSubclass(StopAsyncIteration):
else:
self.fail(f'{stop_exc} was suppressed')

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_contextmanager_wrap_runtimeerror(self):
@asynccontextmanager
async def woohoo():
Expand Down Expand Up @@ -346,17 +326,13 @@ def test_contextmanager_doc_attrib(self):
baz = self._create_contextmanager_attribs()
self.assertEqual(baz.__doc__, "Whee!")

# TODO: RUSTPYTHON
@unittest.expectedFailure
@support.requires_docstrings
async def test_instance_docstring_given_cm_docstring(self):
baz = self._create_contextmanager_attribs()(None)
self.assertEqual(baz.__doc__, "Whee!")
async with baz:
pass # suppress warning

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_keywords(self):
# Ensure no keyword arguments are inhibited
@asynccontextmanager
Expand All @@ -365,8 +341,6 @@ async def woohoo(self, func, args, kwds):
async with woohoo(self=11, func=22, args=33, kwds=44) as target:
self.assertEqual(target, (11, 22, 33, 44))

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_recursive(self):
depth = 0
ncols = 0
Expand All @@ -393,8 +367,6 @@ async def recursive():
self.assertEqual(ncols, 10)
self.assertEqual(depth, 0)

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_decorator(self):
entered = False

Expand All @@ -413,8 +385,6 @@ async def test():
await test()
self.assertFalse(entered)

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_decorator_with_exception(self):
entered = False

Expand All @@ -437,8 +407,6 @@ async def test():
await test()
self.assertFalse(entered)

# TODO: RUSTPYTHON
@unittest.expectedFailure
async def test_decorating_method(self):

@asynccontextmanager
Expand Down
16 changes: 16 additions & 0 deletions vm/src/stdlib/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,22 @@ mod builtins {
iter_target.get_aiter(vm)
}

#[pyfunction]
fn anext(
aiter: PyObjectRef,
default_value: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult {
let awaitable = vm.call_method(&aiter, "__anext__", ())?;

if default_value.is_missing() {
Ok(awaitable)
} else {
// TODO: Implement CPython like PyAnextAwaitable to properly handle the default value.
Ok(awaitable)
}
}
Comment on lines +537 to +551
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incomplete default value handling makes the function only partially functional.

The default_value parameter is accepted but completely ignored—both branches return the same awaitable regardless of whether a default is provided. This causes the function to fail the second use case described in the PR objectives: when the async iterator is exhausted and a default value is provided, RustPython raises StopAsyncIteration instead of returning the default like CPython does.

While the TODO comment acknowledges this limitation, having a function signature that accepts a parameter it cannot honor is problematic because:

  • Users expect CPython-compatible behavior
  • The silent acceptance of default_value creates a false sense of functionality
  • It's better to fail fast than fail silently

Consider one of these approaches before merging:

Option 1 (preferred if feasible): Complete the implementation by adding PyAnextAwaitable to wrap the awaitable and handle the default value case.

Option 2: Reject the default parameter explicitly until support is implemented:

 #[pyfunction]
 fn anext(
     aiter: PyObjectRef,
     default_value: OptionalArg<PyObjectRef>,
     vm: &VirtualMachine,
 ) -> PyResult {
     let awaitable = vm.call_method(&aiter, "__anext__", ())?;
 
-    if default_value.is_missing() {
-        Ok(awaitable)
-    } else {
-        // TODO: Implement CPython like PyAnextAwaitable to properly handle the default value.
-        Ok(awaitable)
+    if let OptionalArg::Present(_) = default_value {
+        Err(vm.new_not_implemented_error(
+            "anext() with default value is not yet implemented".to_owned()
+        ))
+    } else {
+        Ok(awaitable)
     }
 }

Option 3: Defer merging this PR until the default value handling is fully implemented.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[pyfunction]
fn anext(
aiter: PyObjectRef,
default_value: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult {
let awaitable = vm.call_method(&aiter, "__anext__", ())?;
if default_value.is_missing() {
Ok(awaitable)
} else {
// TODO: Implement CPython like PyAnextAwaitable to properly handle the default value.
Ok(awaitable)
}
}
#[pyfunction]
fn anext(
aiter: PyObjectRef,
default_value: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult {
let awaitable = vm.call_method(&aiter, "__anext__", ())?;
if let OptionalArg::Present(_) = default_value {
Err(vm.new_not_implemented_error(
"anext() with default value is not yet implemented".to_owned()
))
} else {
Ok(awaitable)
}
}


#[pyfunction]
fn len(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
obj.length(vm)
Expand Down
Loading