| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -4,6 +4,7 @@ aenter | |
| aexit | ||
| aiter | ||
| anext | ||
| anextawaitable | ||
| appendleft | ||
| argcount | ||
| arrayiterator | ||
| Expand Down | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -33,9 +33,9 @@ impl PyAsyncGen { | |
| &self.inner | ||
| } | ||
|
|
||
| pub fn new(frame: FrameRef, name: PyStrRef) -> Self { | ||
| pub fn new(frame: FrameRef, name: PyStrRef, qualname: PyStrRef) -> Self { | ||
| Self { | ||
| inner: Coro::new(frame, name), | ||
| inner: Coro::new(frame, name, qualname), | ||
| running_async: AtomicCell::new(false), | ||
| } | ||
| } | ||
| Expand All | @@ -50,6 +50,16 @@ impl PyAsyncGen { | |
| self.inner.set_name(name) | ||
| } | ||
|
|
||
| #[pygetset] | ||
| fn __qualname__(&self) -> PyStrRef { | ||
| self.inner.qualname() | ||
| } | ||
|
|
||
| #[pygetset(setter)] | ||
| fn set___qualname__(&self, qualname: PyStrRef) { | ||
| self.inner.set_qualname(qualname) | ||
| } | ||
|
|
||
| #[pygetset] | ||
| fn ag_await(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> { | ||
| self.inner.frame().yield_from_target() | ||
| Expand Down Expand Up | @@ -424,8 +434,151 @@ impl IterNext for PyAsyncGenAThrow { | |
| } | ||
| } | ||
|
|
||
| /// Awaitable wrapper for anext() builtin with default value. | ||
| /// When StopAsyncIteration is raised, it converts it to StopIteration(default). | ||
| #[pyclass(module = false, name = "anext_awaitable")] | ||
| #[derive(Debug)] | ||
| pub struct PyAnextAwaitable { | ||
| wrapped: PyObjectRef, | ||
| default_value: PyObjectRef, | ||
| } | ||
|
|
||
| impl PyPayload for PyAnextAwaitable { | ||
| #[inline] | ||
| fn class(ctx: &Context) -> &'static Py<PyType> { | ||
| ctx.types.anext_awaitable | ||
| } | ||
| } | ||
|
|
||
| #[pyclass(with(IterNext, Iterable))] | ||
| impl PyAnextAwaitable { | ||
| pub fn new(wrapped: PyObjectRef, default_value: PyObjectRef) -> Self { | ||
| Self { | ||
| wrapped, | ||
| default_value, | ||
| } | ||
| } | ||
|
|
||
| #[pymethod(name = "__await__")] | ||
| fn r#await(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyRef<Self> { | ||
| zelf | ||
| } | ||
|
|
||
| /// Get the awaitable iterator from wrapped object. | ||
| // = anextawaitable_getiter. | ||
| fn get_awaitable_iter(&self, vm: &VirtualMachine) -> PyResult { | ||
| use crate::builtins::PyCoroutine; | ||
| use crate::protocol::PyIter; | ||
|
|
||
| let wrapped = &self.wrapped; | ||
|
|
||
| // If wrapped is already an async_generator_asend, it's an iterator | ||
| if wrapped.class().is(vm.ctx.types.async_generator_asend) | ||
| || wrapped.class().is(vm.ctx.types.async_generator_athrow) | ||
| { | ||
| return Ok(wrapped.clone()); | ||
| } | ||
|
|
||
| // _PyCoro_GetAwaitableIter equivalent | ||
| let awaitable = if wrapped.class().is(vm.ctx.types.coroutine_type) { | ||
| // Coroutine - get __await__ later | ||
| wrapped.clone() | ||
| } else { | ||
| // Try to get __await__ method | ||
| if let Some(await_method) = vm.get_method(wrapped.clone(), identifier!(vm, __await__)) { | ||
| await_method?.call((), vm)? | ||
| } else { | ||
| return Err(vm.new_type_error(format!( | ||
| "object {} can't be used in 'await' expression", | ||
| wrapped.class().name() | ||
| ))); | ||
| } | ||
| }; | ||
|
|
||
| // If awaitable is a coroutine, get its __await__ | ||
| if awaitable.class().is(vm.ctx.types.coroutine_type) { | ||
| let coro_await = vm.call_method(&awaitable, "__await__", ())?; | ||
| // Check that __await__ returned an iterator | ||
| if !PyIter::check(&coro_await) { | ||
| return Err(vm.new_type_error("__await__ returned a non-iterable")); | ||
| } | ||
| return Ok(coro_await); | ||
| } | ||
|
|
||
| // Check the result is an iterator, not a coroutine | ||
| if awaitable.downcast_ref::<PyCoroutine>().is_some() { | ||
| return Err(vm.new_type_error("__await__() returned a coroutine")); | ||
| } | ||
|
|
||
| // Check that the result is an iterator | ||
| if !PyIter::check(&awaitable) { | ||
| return Err(vm.new_type_error(format!( | ||
| "__await__() returned non-iterator of type '{}'", | ||
| awaitable.class().name() | ||
| ))); | ||
| } | ||
|
|
||
| Ok(awaitable) | ||
| } | ||
|
|
||
| #[pymethod] | ||
| fn send(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { | ||
| let awaitable = self.get_awaitable_iter(vm)?; | ||
| let result = vm.call_method(&awaitable, "send", (val,)); | ||
| self.handle_result(result, vm) | ||
| } | ||
|
|
||
| #[pymethod] | ||
| fn throw( | ||
| &self, | ||
| exc_type: PyObjectRef, | ||
| exc_val: OptionalArg, | ||
| exc_tb: OptionalArg, | ||
| vm: &VirtualMachine, | ||
| ) -> PyResult { | ||
| let awaitable = self.get_awaitable_iter(vm)?; | ||
| let result = vm.call_method( | ||
| &awaitable, | ||
| "throw", | ||
| ( | ||
| exc_type, | ||
| exc_val.unwrap_or_none(vm), | ||
| exc_tb.unwrap_or_none(vm), | ||
| ), | ||
| ); | ||
| self.handle_result(result, vm) | ||
| } | ||
|
|
||
| #[pymethod] | ||
| fn close(&self, vm: &VirtualMachine) -> PyResult<()> { | ||
| if let Ok(awaitable) = self.get_awaitable_iter(vm) { | ||
| let _ = vm.call_method(&awaitable, "close", ()); | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Convert StopAsyncIteration to StopIteration(default_value) | ||
| fn handle_result(&self, result: PyResult, vm: &VirtualMachine) -> PyResult { | ||
| match result { | ||
| Ok(value) => Ok(value), | ||
| Err(exc) if exc.fast_isinstance(vm.ctx.exceptions.stop_async_iteration) => { | ||
| Err(vm.new_stop_iteration(Some(self.default_value.clone()))) | ||
| } | ||
| Err(exc) => Err(exc), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl SelfIter for PyAnextAwaitable {} | ||
| impl IterNext for PyAnextAwaitable { | ||
| fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> { | ||
| PyIterReturn::from_pyresult(zelf.send(vm.ctx.none(), vm), vm) | ||
| } | ||
| } | ||
|
Comment thread
Comment on lines
+437
to
+577
Copy link
Copy Markdown
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality⚠️ Potential issue | 🔴 Critical PyAnextAwaitable is likely functionally wrong: it recreates the underlying __await__ iterator each .send() / .throw() call. That breaks iterator state (and can repeat side effects) because __await__() is expected to be called once, and the returned iterator is then driven until completion. Cache the iterator inside PyAnextAwaitable (and mark “closed” after completion) like other awaitables in this file. Minimal direction (illustrative diff sketch; adjust imports/types as needed): pub struct PyAnextAwaitable {
wrapped: PyObjectRef,
default_value: PyObjectRef,
+ await_iter: crate::common::lock::PyMutex<Option<PyObjectRef>>,
+ state: AtomicCell<AwaitableState>,
}
impl PyAnextAwaitable {
pub fn new(wrapped: PyObjectRef, default_value: PyObjectRef) -> Self {
Self {
wrapped,
default_value,
+ await_iter: crate::common::lock::PyMutex::new(None),
+ state: AtomicCell::new(AwaitableState::Init),
}
}
- fn get_awaitable_iter(&self, vm: &VirtualMachine) -> PyResult {
+ fn get_awaitable_iter(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
+ if let AwaitableState::Closed = self.state.load() {
+ return Err(vm.new_runtime_error("cannot reuse already awaited anext()"));
+ }
+ if let Some(it) = self.await_iter.lock().clone() {
+ return Ok(it);
+ }
...
- Ok(awaitable)
+ *self.await_iter.lock() = Some(awaitable.clone());
+ self.state.store(AwaitableState::Iter);
+ Ok(awaitable)
}🤖 Prompt for AI Agents crates/vm/src/builtins/asyncgenerator.rs around lines 437 to 577: the current implementation calls __await__ (or constructs the awaitable iterator) on every send/throw which recreates the iterator and breaks iterator state; change PyAnextAwaitable to hold an Option<PyObjectRef> field (e.g., awaitable: Option<PyObjectRef>) to cache the iterator, initialize that field the first time get_awaitable_iter is called, return the cached iterator on subsequent calls, and set it to None/mark closed when the iterator completes (StopAsyncIteration) or when close() is called; update get_awaitable_iter, send, throw and close to use and mutate this cached field appropriately, following the same ownership and VM error handling patterns used by other awaitable types in the file.
Sorry, something went wrong.
All reactions
|
||
|
|
||
| pub fn init(ctx: &Context) { | ||
| PyAsyncGen::extend_class(ctx, ctx.types.async_generator); | ||
| PyAsyncGenASend::extend_class(ctx, ctx.types.async_generator_asend); | ||
| PyAsyncGenAThrow::extend_class(ctx, ctx.types.async_generator_athrow); | ||
| PyAnextAwaitable::extend_class(ctx, ctx.types.anext_awaitable); | ||
| } | ||
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality⚠️ Potential issue | 🟠 Major
PyAnextAwaitable assumes the await-iterator has send/throw; CPython allows plain iterators from __await__.
If RustPython’s await-driving machinery can handle plain iterators, PyAnextAwaitable.send() should fall back to advancing via __next__ (when val is None) if there’s no send, and throw() should propagate if there’s no throw—otherwise this wrapper will reject valid __await__ implementations.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.