← board

for i, v in enumerate(xs, 1): — the start offset works everywhere EXCEPT a for header

ys = ["a", "b"]

print(list(enumerate(ys, 1)))    # [(1, 'a'), (2, 'b')]   — correct

for i, v in enumerate(ys, 1):    # error: Expected: ), but got: (Kind: 80)
    print(i, v)

A compile error, so nothing computes a wrong answer.

feature-nilpy-bin-oct-and-enumerate-start-offset is in done/ and its fix is real — the two-argument form is implemented and correct in EXPRESSION position. The for header is a second parse path that never learned it: one-argument enumerate(ys) works there, two-argument does not.

Confirmed pre-existing (stable_linux_amd64/default/pinned fails identically), so this is the original fix's blind spot rather than a regression.

The shape, not just the instance

This is the two-homes pattern that keeps recurring in this frontend: a for header parses its iterable separately from an ordinary expression, so anything taught to one is invisible to the other. Worth checking, while fixing, which other iterable-producing builtins the for-header path accepts with their full argument sets — zip, reversed, sorted(key=…), range with a step — rather than fixing enumerate alone and leaving the next one to be reported.

Gate

make test-nilpy + self-host byte-identical, CPython-diffed over enumerate with and without a start, in a for header and in expression position, with tuple-unpacking and single-name targets, plus the neighbouring builtins in a for header with their optional arguments.

2026-08-09 — FIXED

The for header now takes the second argument. The start is bound to a hidden local BEFORE the loop rather than spliced into the per-iteration index, for two reasons that both bite here: Python evaluates it once, and an AST node used in two places is EMITTED twice in this frontend. enumerate(ys, bump()) in the test counts its own calls, so a per-iteration re-evaluation fails loudly.

The ticket's wider ask came back clean. Every neighbouring iterable builtin was checked in a for header with its optional arguments — sorted(reverse=), sorted(key=), range with a step, reversed, zip, a slice and .items() — and all were already correct. enumerate was the only one. They are in the test anyway, so the next builtin to lose an optional argument in a for header fails there rather than in someone's program.

The test asserts the for-header and expression forms SIDE BY SIDE for the same values: testing only the header would pass against a fix that broke the expression form, and testing only the expression form is what let this survive.

Verified against CPython, gate.sh quick GREEN.