◐ Shell
reader mode source ↗
Skip to content

PyAnextAwaitable #6427

Merged
youknowone merged 2 commits into
RustPython:mainfrom
youknowone:asyncgen
Dec 14, 2025
Merged

PyAnextAwaitable #6427
youknowone merged 2 commits into
RustPython:mainfrom
youknowone:asyncgen

Conversation

@youknowone

@youknowone youknowone commented Dec 12, 2025

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added qualname support for generators, coroutines, and async generators for improved introspection.
    • Introduced a new awaitable wrapper for anext(default) so anext(..., default) returns an awaitable that maps StopAsyncIteration to StopIteration(default).
  • Bug Fixes / Improvements

    • Stricter async-iterator protocol checks with clearer error messages when objects lack or misuse aiter/anext.
  • Chores

    • Added a new spelling token to the project dictionary.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 12, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds qualname storage/accessors for Coro-derived objects, introduces a new PyAnextAwaitable type and registers it, updates builtins.anext to wrap awaitables when a default is provided, and tightens async-iterator protocol checks in get_aiter().

Changes

Cohort / File(s) Summary
Core Coro qualname field
crates/vm/src/coroutine.rs
Added qualname: PyMutex<PyStrRef>, updated Coro::new(frame, name, qualname), and added qualname() / set_qualname() accessors.
Generator / Coroutine / AsyncGen constructors & accessors
crates/vm/src/builtins/generator.rs, crates/vm/src/builtins/coroutine.rs, crates/vm/src/builtins/asyncgenerator.rs
Threaded qualname through constructors to Coro::new(...); added __qualname__ getter and set___qualname__ setter on PyGenerator, PyCoroutine, and PyAsyncGen.
anext awaitable wrapper type
crates/vm/src/builtins/asyncgenerator.rs
Introduced PyAnextAwaitable type with __await__, send/throw/close, StopAsyncIteration→StopIteration(default) handling, iteration protocol support, and class registration.
anext builtin behavior
crates/vm/src/stdlib/builtins.rs
Validates presence of __anext__; when a default is provided, wraps the awaitable returned by __anext__ in PyAnextAwaitable; otherwise returns raw awaitable.
get_aiter protocol validation
crates/vm/src/protocol/object.rs
Reworked get_aiter() to call __aiter__, reject coroutine results, and verify the result has __anext__, emitting specific TypeErrors on failure.
Function constructor call sites
crates/vm/src/builtins/function.rs
Updated call sites to pass qualname into PyGenerator::new, PyCoroutine::new, and PyAsyncGen::new.
Type registration
crates/vm/src/types/zoo.rs
Added anext_awaitable: &'static Py<PyType> to TypeZoo and initialized it via PyAnextAwaitable::init_builtin_type().
Spell dictionary
.cspell.dict/python-more.txt
Added token anextawaitable.

Sequence Diagram

sequenceDiagram
    participant Caller as Caller
    participant Builtin as builtins.anext
    participant Obj as AsyncIterableObj
    participant Awaitable as Awaitable (from __anext__)
    participant Wrapper as PyAnextAwaitable

    Caller->>Builtin: anext(obj, default?)
    Builtin->>Obj: check __anext__ exists
    alt no __anext__
        Builtin-->>Caller: TypeError (not async iterator)
    else has __anext__
        Builtin->>Obj: call __anext__()
        Obj-->>Awaitable: returns awaitable
        alt default provided
            Builtin->>Wrapper: PyAnextAwaitable::new(awaitable, default)
            Builtin-->>Caller: return Wrapper
            Caller->>Wrapper: await (calls __await__/send/throw)
            Wrapper->>Awaitable: delegate send/throw/close
            alt Awaitable raises StopAsyncIteration
                Wrapper-->>Caller: raise StopIteration(default)
            else Awaitable yields value
                Wrapper-->>Caller: return value
            end
        else no default
            Builtin-->>Caller: return Awaitable
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

  • Pay special attention to: PyAnextAwaitable correctness (await protocol, error mapping), all constructor call sites passing qualname, and get_aiter() error messages and coroutine-detection logic.

Possibly related PRs

Suggested reviewers

  • arihant2math

🐰
I hopped through frames and names so fine,
Wrapped anext in a cozy line.
Qualnames threaded through each corer,
Async hops now yield no more chore. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.38% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ⚠️ Warning The title 'PyAnextAwaitable' is overly narrow and does not capture the broader scope of changes, which include qualname support across multiple coroutine/generator types and protocol improvements. Expand the title to reflect the main objectives, such as 'Add qualname support and PyAnextAwaitable wrapper for async generators' or similar to better represent the full scope of changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

Code has been automatically formatted

The code in this PR has been formatted using cargo fmt --all.
Please pull the latest changes before pushing again:

git pull origin asyncgen

@youknowone youknowone force-pushed the asyncgen branch 3 times, most recently from 7fe0cdb to 0199879 Compare December 13, 2025 02:11
@youknowone youknowone marked this pull request as ready for review December 14, 2025 00:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hide comment

Actionable comments posted: 0

🧹 Nitpick comments (3)
crates/vm/src/protocol/object.rs (1)

106-110: Consider including the actual type name in the error message.

The error message uses a hardcoded string 'async_iterator' rather than the actual type name. CPython typically includes the actual type in such messages.

         // Check that __aiter__ did not return a coroutine
         if iterator.downcast_ref::<PyCoroutine>().is_some() {
-            return Err(vm.new_type_error(
-                "'async_iterator' object cannot be interpreted as an async iterable; \
-                perhaps you forgot to call aiter()?",
-            ));
+            return Err(vm.new_type_error(format!(
+                "'{}' object cannot be interpreted as an async iterable; \
+                perhaps you forgot to call aiter()?",
+                iterator.class().name()
+            )));
         }
crates/vm/src/builtins/asyncgenerator.rs (2)

469-522: Redundant coroutine check at line 509.

The downcast_ref::<PyCoroutine>() check on line 509 is unreachable for actual coroutines because line 499 already handles the coroutine_type case and returns early. This check would only catch objects that have a __await__ method returning a PyCoroutine, which is already an error condition.

Consider clarifying the intent or removing the redundant check:

-        // 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 the __await__ result is not a coroutine (invalid usage)
+        if awaitable.class().is(vm.ctx.types.coroutine_type) {
+            return Err(vm.new_type_error("__await__() returned a coroutine"));
+        }

552-558: Consider propagating close errors instead of silently ignoring them.

The close() method silently ignores both errors from get_awaitable_iter() and from the inner close() call. While ignoring get_awaitable_iter errors may be intentional (the awaitable may already be exhausted), silently ignoring close() errors could hide legitimate issues.

     #[pymethod]
     fn close(&self, vm: &VirtualMachine) -> PyResult<()> {
         if let Ok(awaitable) = self.get_awaitable_iter(vm) {
-            let _ = vm.call_method(&awaitable, "close", ());
+            // Ignore errors from close() as is typical for cleanup methods,
+            // but the awaitable may not have a close() method
+            if let Some(close_method) = vm.get_method(awaitable.clone(), identifier!(vm, close)) {
+                let _ = close_method?.call((), vm);
+            }
         }
         Ok(())
     }
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 835918a and 571539d.

⛔ Files ignored due to path filters (3)
  • Lib/test/test_asyncgen.py is excluded by !Lib/**
  • Lib/test/test_generators.py is excluded by !Lib/**
  • Lib/test/test_types.py is excluded by !Lib/**
📒 Files selected for processing (9)
  • .cspell.dict/python-more.txt (1 hunks)
  • crates/vm/src/builtins/asyncgenerator.rs (3 hunks)
  • crates/vm/src/builtins/coroutine.rs (3 hunks)
  • crates/vm/src/builtins/function.rs (1 hunks)
  • crates/vm/src/builtins/generator.rs (2 hunks)
  • crates/vm/src/coroutine.rs (3 hunks)
  • crates/vm/src/protocol/object.rs (2 hunks)
  • crates/vm/src/stdlib/builtins.rs (1 hunks)
  • crates/vm/src/types/zoo.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Follow the default rustfmt code style by running cargo fmt to format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • crates/vm/src/stdlib/builtins.rs
  • crates/vm/src/types/zoo.rs
  • crates/vm/src/builtins/coroutine.rs
  • crates/vm/src/builtins/function.rs
  • crates/vm/src/builtins/generator.rs
  • crates/vm/src/coroutine.rs
  • crates/vm/src/protocol/object.rs
  • crates/vm/src/builtins/asyncgenerator.rs
🧬 Code graph analysis (5)
crates/vm/src/types/zoo.rs (1)
crates/vm/src/class.rs (1)
  • init_builtin_type (39-48)
crates/vm/src/builtins/coroutine.rs (2)
crates/vm/src/builtins/generator.rs (4)
  • new (35-39)
  • __qualname__ (52-54)
  • set___qualname__ (57-59)
  • close (112-114)
crates/vm/src/coroutine.rs (5)
  • new (51-60)
  • frame (180-182)
  • name (184-186)
  • qualname (192-194)
  • close (150-170)
crates/vm/src/builtins/function.rs (2)
crates/vm/src/builtins/coroutine.rs (1)
  • new (32-36)
crates/vm/src/builtins/generator.rs (1)
  • new (35-39)
crates/vm/src/builtins/generator.rs (3)
crates/vm/src/builtins/asyncgenerator.rs (4)
  • new (36-41)
  • new (455-460)
  • __qualname__ (54-56)
  • set___qualname__ (59-61)
crates/vm/src/builtins/coroutine.rs (3)
  • new (32-36)
  • __qualname__ (49-51)
  • set___qualname__ (54-56)
crates/vm/src/builtins/property.rs (1)
  • setter (249-255)
crates/vm/src/protocol/object.rs (1)
crates/vm/src/builtins/asyncgenerator.rs (5)
  • __aiter__ (89-91)
  • class (25-27)
  • class (156-158)
  • class (206-208)
  • __anext__ (94-96)
🔇 Additional comments (21)
.cspell.dict/python-more.txt (1)

7-7: LGTM!

The dictionary entry is correctly alphabetized and corresponds to the new PyAnextAwaitable type introduced in this PR.

crates/vm/src/types/zoo.rs (2)

24-24: LGTM!

The anext_awaitable field is properly added to TypeZoo and follows the established pattern for type registration, grouped appropriately with other async generator related types.


143-143: LGTM!

Initialization correctly uses init_builtin_type() consistent with other type registrations in this file.

crates/vm/src/stdlib/builtins.rs (1)

543-561: LGTM!

The anext function implementation correctly:

  1. Validates that the object is an async iterator by checking for __anext__
  2. Calls __anext__ to get the awaitable
  3. Wraps the result in PyAnextAwaitable when a default value is provided
  4. Returns the raw awaitable when no default is given

The error message appropriately mirrors CPython's behavior for non-async-iterator objects.

crates/vm/src/builtins/function.rs (1)

428-436: LGTM!

The updated constructor calls correctly pass both __name__() and __qualname__() to PyGenerator, PyCoroutine, and PyAsyncGen, aligning with the PR objective to propagate qualified names throughout the generator/coroutine hierarchy.

crates/vm/src/protocol/object.rs (1)

90-120: Enhanced async iterable protocol validation looks good.

The implementation properly:

  1. Checks for __aiter__ presence before calling
  2. Detects incorrectly defined async def __aiter__ methods that return coroutines
  3. Validates the result is an async iterator by checking for __anext__
crates/vm/src/builtins/coroutine.rs (3)

32-36: LGTM!

The constructor update correctly propagates the new qualname parameter to Coro::new, maintaining consistency with PyGenerator::new and PyAsyncGen::new.


48-56: LGTM!

The __qualname__ getter and setter follow the same pattern as the existing __name__ accessors and correctly delegate to the inner Coro methods.


169-173: LGTM!

The close method correctly delegates to the inner coroutine's close method, completing the wrapper's lifecycle management API alongside the existing send and throw methods.

crates/vm/src/coroutine.rs (3)

35-35: LGTM!

The new qualname field appropriately uses PyMutex<PyStrRef> for thread-safe access, consistent with the existing name field.


51-58: LGTM!

The constructor properly initializes the new qualname field alongside existing fields.


192-198: LGTM!

The qualname() getter and set_qualname() setter follow the established pattern of the name() and set_name() methods.

crates/vm/src/builtins/generator.rs (2)

35-39: LGTM!

The constructor update correctly propagates qualname to Coro::new, consistent with the parallel changes in PyCoroutine and PyAsyncGen.


51-59: LGTM!

The __qualname__ getter and setter correctly delegate to the inner Coro methods, following the same pattern as the __name__ accessors.

crates/vm/src/builtins/asyncgenerator.rs (7)

36-41: LGTM!

The constructor update correctly propagates qualname to Coro::new, consistent with the parallel changes in PyCoroutine and PyGenerator.


53-61: LGTM!

The __qualname__ getter and setter correctly delegate to the inner Coro methods, maintaining consistency with the other generator types.


437-460: LGTM!

The PyAnextAwaitable struct is well-documented and follows the established pattern for implementing Python types in RustPython.


561-569: LGTM!

The handle_result method correctly converts StopAsyncIteration to StopIteration(default_value), which is the core functionality needed for anext() with a default value.


572-577: LGTM!

The SelfIter and IterNext implementations follow the established pattern used by other async generator helper types.


583-583: LGTM!

The new PyAnextAwaitable type is properly registered in the init function.


524-549: get_awaitable_iter() is called on every send()/throw() invocation, but this is correct and necessary.

PyAnextAwaitable wraps arbitrary async iterators (unlike PyAsyncGenASend which handles only async generators directly). For the common case of wrapping async generators, get_awaitable_iter() returns immediately via an early check. For coroutines and other awaitables, the method must call __await__() fresh on each invocation per PEP 492 requirements — caching the result would violate the protocol by reusing an exhausted iterator. The current design correctly handles all awaitable types without unnecessary overhead.

Likely an incorrect or invalid review comment.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hide comment

Actionable comments posted: 4

🧹 Nitpick comments (1)
crates/vm/src/coroutine.rs (1)

200-207: Consider using qualname (not name) in Coro::repr() for CPython parity.

If RustPython aims to match CPython’s <coroutine object outer.<locals>.inner at ...> style, self.qualname is typically the right display name (and would respect __qualname__ mutation as well).

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 571539d and 455d3f6.

⛔ Files ignored due to path filters (3)
  • Lib/test/test_asyncgen.py is excluded by !Lib/**
  • Lib/test/test_generators.py is excluded by !Lib/**
  • Lib/test/test_types.py is excluded by !Lib/**
📒 Files selected for processing (9)
  • .cspell.dict/python-more.txt (1 hunks)
  • crates/vm/src/builtins/asyncgenerator.rs (3 hunks)
  • crates/vm/src/builtins/coroutine.rs (3 hunks)
  • crates/vm/src/builtins/function.rs (1 hunks)
  • crates/vm/src/builtins/generator.rs (2 hunks)
  • crates/vm/src/coroutine.rs (3 hunks)
  • crates/vm/src/protocol/object.rs (2 hunks)
  • crates/vm/src/stdlib/builtins.rs (1 hunks)
  • crates/vm/src/types/zoo.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/vm/src/types/zoo.rs
  • .cspell.dict/python-more.txt
  • crates/vm/src/builtins/function.rs
  • crates/vm/src/stdlib/builtins.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Follow the default rustfmt code style by running cargo fmt to format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • crates/vm/src/protocol/object.rs
  • crates/vm/src/builtins/coroutine.rs
  • crates/vm/src/builtins/asyncgenerator.rs
  • crates/vm/src/builtins/generator.rs
  • crates/vm/src/coroutine.rs
🧬 Code graph analysis (3)
crates/vm/src/protocol/object.rs (2)
crates/vm/src/builtins/asyncgenerator.rs (5)
  • __aiter__ (89-91)
  • class (25-27)
  • class (156-158)
  • class (206-208)
  • __anext__ (94-96)
crates/vm/src/coroutine.rs (1)
  • name (184-186)
crates/vm/src/builtins/coroutine.rs (4)
crates/vm/src/builtins/asyncgenerator.rs (7)
  • new (36-41)
  • new (455-460)
  • __qualname__ (54-56)
  • set___qualname__ (59-61)
  • close (277-279)
  • close (401-403)
  • close (553-558)
crates/vm/src/builtins/function.rs (6)
  • new (58-91)
  • new (789-791)
  • new (911-915)
  • __qualname__ (567-569)
  • __qualname__ (836-855)
  • set___qualname__ (572-585)
crates/vm/src/builtins/generator.rs (4)
  • new (35-39)
  • __qualname__ (52-54)
  • set___qualname__ (57-59)
  • close (112-114)
crates/vm/src/coroutine.rs (5)
  • new (51-60)
  • frame (180-182)
  • name (184-186)
  • qualname (192-194)
  • close (150-170)
crates/vm/src/coroutine.rs (4)
crates/vm/src/builtins/asyncgenerator.rs (2)
  • new (36-41)
  • new (455-460)
crates/vm/src/builtins/coroutine.rs (1)
  • new (32-36)
crates/vm/src/builtins/function.rs (3)
  • new (58-91)
  • new (789-791)
  • new (911-915)
crates/vm/src/builtins/generator.rs (1)
  • new (35-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: Check the WASM package and demo
🔇 Additional comments (9)
crates/vm/src/builtins/coroutine.rs (3)

32-56: Qualname plumbing on PyCoroutine looks consistent with Coro and other generator-like types.


169-173: Wrapper close() delegation is a good parity improvement with generator wrappers.


32-36: All call sites have been properly updated to the new constructor signature.

The search for any remaining calls to PyCoroutine::new with the old 2-parameter signature found no matches, confirming that all call sites throughout the codebase (including tests and stdlib) have been updated to use the new 3-parameter signature (frame: FrameRef, name: PyStrRef, qualname: PyStrRef).

crates/vm/src/coroutine.rs (1)

27-60: Coro qualname storage/accessors are straightforward and match the intended propagation pattern.

Also applies to: 192-198

crates/vm/src/builtins/generator.rs (2)

35-60: Generator qualname API mirrors coroutine changes cleanly.


35-39: All call sites of PyGenerator::new are properly updated. The constructor at lines 35-39 accepts three parameters (frame, name, qualname), and the single call site in crates/vm/src/builtins/function.rs:429 correctly passes all three arguments.

crates/vm/src/builtins/asyncgenerator.rs (3)

36-61: Async generator qualname changes align with the generator/coroutine pattern.


579-584: Type registration wiring looks correct (extend_class for anext_awaitable).


1-12: Please ensure cargo fmt + cargo clippy are clean for these protocol/runtime changes.

Also applies to: 579-584

@youknowone youknowone changed the title more asyncgen impl Dec 14, 2025
Hide details View details @youknowone youknowone merged commit b3c2aa6 into RustPython:main Dec 14, 2025
13 checks passed
@youknowone youknowone deleted the asyncgen branch December 14, 2025 03:15
@coderabbitai coderabbitai Bot mentioned this pull request Dec 15, 2025
This was referenced Jan 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant