Lowering for the explicit iterator protocol on statically-sized
sequences.
Handles the pattern ``it = seq.__iter__()`` followed by ``next(it)`` or
``it.__next__()``. The ``__iter__`` call already returns the sequence
object itself (see ``__iter__ on builtin iterables`` in the C/C++ frontend),
so ``it`` aliases the sequence and ``it[k]`` reads its k-th element. Each
``next``/``__next__`` is rewritten to ``it[k]`` with a per-iterator
compile-time index, raising ``StopIteration`` once the captured length is
exhausted (CPython semantics).
Because the consumption index is tracked statically in source order, the
rewrite is only sound for iterators consumed in straight-line code. The
pre-scan (``_scan_sequence_iterators``) blacklists any iterator whose
binding or consumption is inside a loop, comprehension, or conditional, or
that is rebound, consumed in a non-rewritable position, or closed over
across scopes; blacklisted iterators are left untouched (falling back to the
frontend's existing behaviour, so there is no regression).
| preprocessor.sequence_iterator_mixin.SequenceIteratorMixin._scan_sequence_iterators |
( |
| self, |
|
|
| node ) |
|
protected |
Blacklist iterator names that cannot be lowered with a static index.
Static index tracking is only sound when the binding and every
consumption of an iterator run exactly once, in source order, in the
same scope. An iterator is disqualified when any of the following holds:
* it is bound or consumed inside a *non-linear* construct — a loop,
comprehension, or conditional (`if`/`try`/`with`/`match`) — where
source order no longer matches runtime execution order;
* a consumption appears in a position the rewriter does not handle
(anything other than a bare ``x = next(it)`` / ``next(it)``
statement), e.g. a call argument, ``return``, or nested expression,
because the real ``next()`` left in place would advance the live
iterator out of step with the static index;
* it is rebound (assigned more than once); or
* its binding and a consumption are in different scopes (a closure
over an outer iterator, which may run any number of times).
Blacklisted iterators are left untouched, falling back to the frontend's
existing behaviour.
| preprocessor.sequence_iterator_mixin.SequenceIteratorMixin._static_len_of |
( |
| self, |
|
|
| node ) |
|
protected |
Static length of ``node`` as a sequence, or None if not known.
Only *immutable* sequences qualify: ``range(<const>)`` calls, tuple
literals, and names previously bound to either. Lists are excluded on
purpose — an iterator aliases the underlying list object, so a mutation
between two ``next()`` calls (``xs.append(...)``) would make a captured
static length diverge from CPython's live iterator. range and tuple
cannot be mutated in valid Python, so their length is fixed at binding.