Skip to content

fix: skip the else of a for or while loop left by break - #59

Merged
medvednikov merged 1 commit into
vlang:masterfrom
ylluminate:fix-for-else
Nov 13, 2026
Merged

medvednikov merged 1 commit into
vlang:masterfrom
ylluminate:fix-for-else

Conversation

@ylluminate

Copy link
Copy Markdown
Contributor

The else of a for loop ran even when the loop was left by break. has_break := false was emitted before the loop, but nothing ever set it:

for x in [1, 2, 3]:
    if x == 2:
        print("found")
        break
else:
    print("missing")

printed found and then missing. The else of a while loop was dropped entirely, so while i < 3: i += 1 with else: print("done", i) printed nothing.

Now a break that leaves a loop with an else sets a flag named for that loop (py2v_broke1), and the else body runs only when the flag is still false (a while True: loop keeps its plain break, since its else can never run). A break in a nested loop sets that loop's own flag or none, and a break inside the else body belongs to the enclosing loop. A while loop with a plain condition and no break of its own runs its else body right after the loop. A loop without an else is emitted exactly as before, and so is a for/else with no break of its own, with one exception: over three or more tuple targets (for a, b, c in rows:) master dropped the else, and it now runs.

Two for/else loops without a break, one after the other in the same block, still both declare has_break and fail to build, as on master. while/else with a walrus condition still drops the else, and async for/else over a call still always runs it, as on master.

Test plan

  • tests/cases/for_else_break.py with its expected V: it covers a break taken and not taken, a break in a nested loop, and while/else with and without a break. What master emits for it doesn't build; with this change it prints what CPython prints.
  • v tests/run_tests.vsh: 170 passed, 0 failed (on master the new case fails and everything else passes). The other 180 Python files under tests/ emit the same bytes as master.
  • 456 generated programs (for over a list, range, range with a step, enumerate and a string, and while; break taken, not taken, in a nested if, in a nested loop, in a nested loop's else, with continue, return and while True; with and without else; one loop or two in a row), compared with CPython's output (V ec510e2, Python 3.15.2, macOS arm64): 88 that were wrong or failed to build now match, and every program that matched before still does. The 168 of them without an else emit the same bytes as master. Of the 208 programs that still don't match, 192 fail under v -W run on both, 14 print the wrong thing on both, and 2 went from failing to printing the wrong thing; all 16 of the wrong ones loop over a string, which V iterates as bytes.

@medvednikov
medvednikov merged commit 64e5287 into vlang:master Nov 13, 2026
2 checks passed
@ylluminate
ylluminate deleted the fix-for-else branch November 13, 2026 07:12
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