From 634cf9bc7fec4ffe15a2c5c6c76896083140d5a5 Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 1 Jun 2026 23:29:07 +0300 Subject: [PATCH] =?UTF-8?q?fix(parser):=20parse=20braced=20`defer=20{=20?= =?UTF-8?q?=E2=80=A6=20}`=20body=20as=20a=20statement=20block=20(issue=200?= =?UTF-8?q?065)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A braced `defer` body routed through `parseExpr` + a mandatory trailing `;`, so it parsed the `{ … }` as a block-EXPRESSION whose statement loop doesn't handle a destructure decl or a `catch`-statement — `defer { v, e := f(); … }` and `defer { x() catch e … }` failed with "expected ';'", and even `defer { stmt; }` needed a spurious trailing semicolon. Now the `kw_defer` arm parses a braced body with `parseBlock` (the same path `onfail` uses), so every statement form works; the bare-expression form (`defer expr;`) is unchanged. `in_defer_body` is still set before parsing, so the cleanup-body control-flow bans (return/break/continue/ try/raise) and the E1.7 failable-absorption check still fire. Resolves the `defer` manifestation of issue 0065 (the general value-block-in-binding-position destructure remains open). Regression: examples/1050-errors-defer-block-body.sx. Gates: zig build, zig build test, run_examples.sh -> 341 passed, 0 failed. --- examples/1050-errors-defer-block-body.sx | 27 +++++++++++++++++++ .../1050-errors-defer-block-body.exit | 1 + .../1050-errors-defer-block-body.stderr | 1 + .../1050-errors-defer-block-body.stdout | 3 +++ .../0065-block-expr-destructure-decl-parse.md | 10 +++++++ src/parser.zig | 14 +++++++--- 6 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 examples/1050-errors-defer-block-body.sx create mode 100644 examples/expected/1050-errors-defer-block-body.exit create mode 100644 examples/expected/1050-errors-defer-block-body.stderr create mode 100644 examples/expected/1050-errors-defer-block-body.stdout diff --git a/examples/1050-errors-defer-block-body.sx b/examples/1050-errors-defer-block-body.sx new file mode 100644 index 0000000..f0c5716 --- /dev/null +++ b/examples/1050-errors-defer-block-body.sx @@ -0,0 +1,27 @@ +// A braced `defer { … }` body parses as a full statement block (like `onfail`), +// so it supports every statement form — a destructure decl, a `catch`-statement, +// nested var decls — not just a single bare expression. Previously `defer { … }` +// routed through the expression parser and rejected those with "expected ';'". +// +// Regression (issue 0065). + +#import "modules/std.sx"; + +E :: error { Bad } + +probe :: () -> (s32, !E) { return 21; } +failing :: () -> !E { raise error.Bad; } + +run :: () { + defer { + v, e := probe(); // destructure decl + if !e { print("defer: v={}\n", v); } // value live under the guard + failing() catch x print("defer: caught\n"); // catch-statement absorbs + } + print("body\n"); +} + +main :: () -> s32 { + run(); + return 0; +} diff --git a/examples/expected/1050-errors-defer-block-body.exit b/examples/expected/1050-errors-defer-block-body.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/1050-errors-defer-block-body.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/1050-errors-defer-block-body.stderr b/examples/expected/1050-errors-defer-block-body.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/1050-errors-defer-block-body.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/1050-errors-defer-block-body.stdout b/examples/expected/1050-errors-defer-block-body.stdout new file mode 100644 index 0000000..0faeb53 --- /dev/null +++ b/examples/expected/1050-errors-defer-block-body.stdout @@ -0,0 +1,3 @@ +body +defer: v=21 +defer: caught diff --git a/issues/0065-block-expr-destructure-decl-parse.md b/issues/0065-block-expr-destructure-decl-parse.md index 6551582..da8fd35 100644 --- a/issues/0065-block-expr-destructure-decl-parse.md +++ b/issues/0065-block-expr-destructure-decl-parse.md @@ -1,5 +1,15 @@ # 0065 — block-expression body does not parse a destructure decl (`v, e := f();`) +> **PARTIALLY RESOLVED (defer manifestation, this session).** A braced +> `defer { … }` body now parses via `parseBlock` (src/parser.zig, the +> `kw_defer` arm) instead of `parseExpr`, mirroring `onfail`. So +> `defer { v, e := f(); … }`, `defer { x() catch e … }`, and plain +> `defer { stmt; }` all parse and run. Regression: +> `examples/1050-errors-defer-block-body.sx`. **Still open:** the +> general *value-producing block in binding position* +> (`y := { v, e := f(); v };`) — a distinct parser path — does not parse +> a destructure decl; see the second reproduction below. + ## Symptom A destructure declaration (`v, e := f();`) inside a **block used in diff --git a/src/parser.zig b/src/parser.zig index b261c5a..d85de7b 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -2019,15 +2019,23 @@ pub const Parser = struct { return try self.createNode(start, .{ .return_stmt = .{ .value = value } }); } - // Defer statement: defer ; + // Defer statement: defer { body } | defer ; + // A braced body parses as a full statement block (like `onfail`), so it + // supports every statement form (destructure, `catch`-statement, …); the + // bare-expression form keeps its trailing `;`. if (self.current.tag == .kw_defer) { const start = self.current.loc.start; self.advance(); const saved_defer = self.in_defer_body; self.in_defer_body = true; defer self.in_defer_body = saved_defer; - const deferred = try self.parseExpr(); - try self.expect(.semicolon); + const deferred: *Node = if (self.current.tag == .l_brace) + try self.parseBlock() + else blk: { + const e = try self.parseExpr(); + try self.expect(.semicolon); + break :blk e; + }; return try self.createNode(start, .{ .defer_stmt = .{ .expr = deferred } }); }