Skip to content

experimental-inspect: Iterator __next__ not optional - #6274

Open
jonasdedden wants to merge 1 commit into
PyO3:mainfrom
jonasdedden:experimental-inspect-next-not-optional
Open

experimental-inspect: Iterator __next__ not optional#6274
jonasdedden wants to merge 1 commit into
PyO3:mainfrom
jonasdedden:experimental-inspect-next-not-optional

Conversation

@jonasdedden

@jonasdedden jonasdedden commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

PyO3 lets __next__ be written as -> Option<T>, where None means "raise StopIteration". Introspection resolved that through the ordinary return-type path, so the stub said:

    def __next__(self, /) -> int | None: ...

Which is wrong in a way that costs more than Incomplete would. mypy takes __next__'s return type as the iteration item type, so every for x in it binds int | None, and the class stops satisfying Iterator[int].

@davidhewitt davidhewitt mentioned this pull request Aug 3, 2026
8 tasks
Comment thread src/impl_/introspection.rs Outdated
Comment on lines +31 to +53
/// The Python type an iterator's `__next__` produces.
///
/// PyO3 lets `__next__` be written as `-> Option<T>` (or `-> PyResult<Option<T>>`), where `None`
/// means "raise `StopIteration`" rather than "yield `None`". At the Python level that is not a
/// returned value at all, so the stub has to say `T`: a stub saying `T | None` makes the loop
/// variable optional and stops the class satisfying `Iterator[T]`.
///
/// The unwrapping is done with an inherent const on the concrete shapes, which takes priority over
/// the trait const used for everything else. Everything that is not an `Option` — an iterator whose
/// `__next__` always yields and signals exhaustion by raising — falls through to
/// [`PyReturnType`] unchanged.
pub struct IterNextOutput<T>(PhantomData<T>);

/// The fallback used when the return type is not an `Option`.
pub trait IterNextOutputFallback: iter_next::Sealed {
/// The function return type
const OUTPUT_TYPE: PyStaticExpr;
}

mod iter_next {
pub trait Sealed {}
impl<T> Sealed for super::IterNextOutput<T> {}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems unfortunate that this machinery is not built on the same structures handling IterNextOutput in impl_/pymethods.rs

Having it build in the same machinery would avoid any accidental future divergence. We might need to move that machinery to use const-generic specialization, however. Not sure.

Comparing to that machinery immediately shows that __anext__ has the same problem.

@jonasdedden

Copy link
Copy Markdown
Contributor Author

Hey @davidhewitt, thanks for your review! Unfortunately I won't have much time until beginning of next week, and I also am not enough of an expert yet in this codebase to accurately judge with which alternative implementation to proceed.

The best I came up with is the following exploration:

  • General Test harness (branching of current main, as this PR has some merge conflicts) +162 -2

Then, there are 3 different solutions I explored branching of the test harness above:

Solutions A & B clear up some code duplication, while C basically keeps existing things as-is.

=> If this input is helpful, you could tell me which direction looks promising. Also feel free to commit and/or use this PR as much as you like, if needed (I saw you wanted to have this included in the next release). If you don't find time and above commits understandibly are a bit too much to review, I could have a more thorough and concentrated look next week!

@davidhewitt

Copy link
Copy Markdown
Member

I prefer solution A please

…anext__` shapes

Route A: one wrapper carrying both the conversion and the type hint

Review pass on the `IterNextOutput` wrapper

pytests: assert the generated `__next__` / `__anext__` hints
@jonasdedden
jonasdedden force-pushed the experimental-inspect-next-not-optional branch from 3741b0e to ffad12d Compare August 7, 2026 10:29
@jonasdedden

Copy link
Copy Markdown
Contributor Author

Implemented the test harness + solution A

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.

2 participants