Skip to content

Python face: trailing ; dropped on the last statement of a while/for loop body #683

Description

@hyperpolymath

Repro

mod.pyaff:

# face: python
def score(n: Int) -> Int:
    let mut sum = 0
    let mut i = 1
    while i <= n:
        sum = sum + i
        i = i + 1
    sum
affinescript compile mod.pyaff -o mod.wasm    # parse error
affinescript preview-python mod.pyaff          # shows the bad canonical:
    while i <= n {
        sum = sum + i;
        i = i + 1        <-- NO trailing ';'
}
    sum

The last statement of the while body (i = i + 1) is emitted without a ;, so it is parsed as the block's tail value expression — but a loop body is not value-returning, and i = i + 1 is a statement.

Cause

lib/python_face.ml tail-position detection (comment at ~line 229: "a regular statement … in the last position of a block … drops the ;"). Dropping the ; on a block's tail line is correct for a def body or an if/else value-arm (the tail IS the return value — this is why recursive face programs like fac compile + run fine), but wrong for a while/for body, where the tail line is a statement that still needs its ;.

Fix direction

The face must know the enclosing block-opener kind when deciding whether the tail line drops its ;: drop it for def/if/else/match-arm value bodies, keep it for while/for bodies (and arguably any block whose value is unused). is_block_opener / the block-opener set already exist (lib/python_face.ml:185-196); thread the opener kind into the tail-position decision.

Impact / severity

Blocks any face-authored program with a loop — directly relevant to the mod/plugin wedge (mods have update loops). Recursive face programs are unaffected (proven: fac(5)=120 compiles through the face and runs). Workaround until fixed: write loops in the canonical .affine surface, or use recursion in the face.

Discovered

2026-07-07, proving a Python-face program end-to-end to wasm.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions