Files
sx/issues/0065-block-expr-destructure-decl-parse.md
agra d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00

3.6 KiB

0065 — block-expression body does not parse a destructure decl (v, e := f();)

RESOLVED. Two fixes landed:

  • The braced defer { … } body now parses via parseBlock (src/parser.zig, the kw_defer arm) instead of parseExpr, mirroring onfail. Regression: examples/1050-errors-defer-block-body.sx (commit 634cf9b).
  • The general value-producing block in binding position fell out of the trailing-; block-value rework: value-position { … } now routes through the same statement parser as every other block, so a destructure decl (and any statement form) parses, and the trailing expression is the block's value. Regression: examples/0042-basic-block-value-destructure.sx.

Symptom

A destructure declaration (v, e := f();) inside a block used in expression position fails to parse with expected ';'. Two surfaced forms:

  • defer { v, e := f(); ... } — a defer body is parsed via parseExpr (so its { ... } is a block-EXPRESSION), and the block-expression statement loop doesn't recognize the name, name := destructure form.
  • y := { v, e := f(); v }; — a value-producing block bound to a name.

Observed: error: expected ';' pointing at the statement after the destructure (the parser bails at the := and resyncs). Expected: the destructure parses exactly as it does in a normal statement block (an if body, a plain { } statement block, or an onfail { } body — all of which use parseBlock and handle it fine).

This is the same family as the pre-existing "value-producing block body in binding position doesn't parse" note in current/CHECKPOINT-ERR.md (E2.4b log). onfail { } is unaffected because it parses its body with parseBlock (src/parser.zig ~2063); defer is affected because it uses parseExpr (~2029).

Reproduction

#import "modules/std.sx";

E :: error { Bad }
val :: () -> (i32, !E) { return 5; }

f :: () -> !E {
    defer {
        v, e := val();          // ← error: expected ';'
        print("v={}\n", v);
    }
    return;
}

main :: () -> i32 { return 0; }

Also reproduces with no defer, as a plain value block:

y := {
    v, e := val();              // ← error: expected ';'
    v
};

Investigation prompt

The block-expression statement loop (the parser path reached from parseExpr when it hits { — see src/parser.zig, the block-as-value parsing around the parsePrimary/parseBlockExpr path, distinct from parseBlock at ~1931) parses each inner statement but does not run the destructure-decl detection that parseStmt does. Find where parseStmt/parseBlock recognizes the ident (, ident)+ := lookahead and make the block-expression statement loop use the same statement parser (ideally route block-expression bodies through parseStmt so every statement form — destructure, var/const decl, etc. — is handled uniformly).

For defer specifically: the simplest aligned fix is to parse a braced defer body with parseBlock (like onfail does) while keeping the bare-expression form (defer expr;) on parseExpr. That removes the defer-body manifestation even if the general block-expression path is handled separately.

Verification: run the repro above — expect it to compile and run (exit 0), with the destructure-bound value usable under an if !e { … } guard (ERR E1.8). Add a regression example under examples/ once fixed.

Status

OPEN. Orthogonal to ERR E1.7/E1.8 — the spec'd cleanup-body absorbers are catch / or <value> (both parse fine in a defer body), so this does not block the error-handling work. Filed while implementing E1.7.