A block's value is now its last statement ONLY when that statement is a trailing expression with no `;`. A trailing `;` discards the value, leaving the block void. This makes value-vs-statement explicit and lets the compiler reject "this block was supposed to produce a value". Compiler: - Parser records `Block.produces_value` (last stmt is a no-`;` trailing expression) + `Block.discarded_semi` (the `;` that discarded a value), via `expectSemicolonAfter`. A trailing expression before `}` may now omit its `;` (previously a parse error). Match-arm and else-arm bodies are built value-producing regardless of the arm `;` (arms are exempt — the `;` is an arm terminator). - Lowering: `lowerBlockValue` / the block-expr path / `inferExprType` respect `produces_value`. A value-position block that discards its value is a hard error (`lowerValueBody` for function bodies; the value-context `.block` path for if/else branches, `catch` bodies, value bindings, match arms). Pure-failable `-> !` bodies (value rides the error channel) and a value-if whose branches are void are handled without false errors. - `defer`/`onfail` cleanup bodies lower as statements (void), so a trailing `;` there is fine. Migration (behavior-preserving — output unchanged): - stdlib + ~210 examples: dropped the trailing `;` on value-position last expressions. `format` now ends with an explicit `#insert "return result;"` (it relied on `#insert`-as-block-value, which `;` discards). - Two `main :: () -> s32` examples that relied on the old silent default-return got an explicit trailing `0`. - Rejection snapshots 0412 / 1013 regenerated (their quoted source lines lost a `;`); the diagnostics themselves are unchanged. Docs/tests: specs.md "Block values" section; examples 0040 (rules) + 0041 (rejection); 3 parser unit tests. Filed issue 0066 (pre-existing match-arm negated-literal phi-width quirk, surfaced not caused here). Gates: zig build, zig build test, run_examples.sh -> 343 passed, cross_compile.sh -> 7 passed (also refreshed its stale example names).
76 lines
3.4 KiB
Plaintext
76 lines
3.4 KiB
Plaintext
// Phase 0 baseline (PLAN-FFI.md step 0.2): small structs (≤16 bytes)
|
||
// passed by value into a C `#foreign` fn and returned by value. Four
|
||
// shapes that exercise distinct aggregate ABI paths:
|
||
// Vec2 — 8 B, two f32 (register pair, float)
|
||
// Vec4f — 16 B, four f32 (HFA — homogeneous float aggregate)
|
||
// Pair64 — 16 B, two s64 (9..16 B int — [2 x i64] coercion slot)
|
||
// Quad32 — 16 B, four s32 (9..16 B int — same slot as Pair64)
|
||
//
|
||
// Pair64 / Quad32 were originally excluded (LLVM verifier rejected the
|
||
// struct<->[2 x i64] type mismatch — see git history for issue-0036);
|
||
// folded back in once emit_llvm.zig's coerceArg learned to bridge
|
||
// struct <-> array via the abi.struct2arr / abi.arr2struct branches.
|
||
|
||
#import "modules/std.sx";
|
||
|
||
// `#source` only — c_import would rewrite struct-typed params/returns
|
||
// in the .h to *void (its struct/opaque pointer default), losing the
|
||
// by-value ABI. The hand-written #foreign decls below keep sx's
|
||
// struct types end-to-end.
|
||
#import c {
|
||
#source "1210-ffi-02-small-struct.c";
|
||
};
|
||
|
||
Vec2 :: struct { x: f32; y: f32; }
|
||
Vec4f :: struct { x: f32; y: f32; z: f32; w: f32; }
|
||
Pair64 :: struct { a: s64; b: s64; }
|
||
Quad32 :: struct { a: s32; b: s32; c: s32; d: s32; }
|
||
|
||
ffi_vec2_make :: (x: f32, y: f32) -> Vec2 #foreign;
|
||
ffi_vec2_swap :: (v: Vec2) -> Vec2 #foreign;
|
||
ffi_vec2_sum :: (v: Vec2) -> f32 #foreign;
|
||
|
||
ffi_vec4f_make :: (x: f32, y: f32, z: f32, w: f32) -> Vec4f #foreign;
|
||
ffi_vec4f_reverse :: (v: Vec4f) -> Vec4f #foreign;
|
||
ffi_vec4f_sum :: (v: Vec4f) -> f32 #foreign;
|
||
|
||
ffi_pair64_make :: (a: s64, b: s64) -> Pair64 #foreign;
|
||
ffi_pair64_swap :: (p: Pair64) -> Pair64 #foreign;
|
||
ffi_pair64_sum :: (p: Pair64) -> s64 #foreign;
|
||
|
||
ffi_quad32_make :: (a: s32, b: s32, c: s32, d: s32) -> Quad32 #foreign;
|
||
ffi_quad32_reverse :: (q: Quad32) -> Quad32 #foreign;
|
||
ffi_quad32_sum :: (q: Quad32) -> s32 #foreign;
|
||
|
||
main :: () -> s32 {
|
||
// ── Vec2 (8 bytes, float pair) ─────────────────────────────────
|
||
v := ffi_vec2_make(1.5, 2.5);
|
||
print("vec2 make = ({}, {})\n", v.x, v.y);
|
||
w := ffi_vec2_swap(v);
|
||
print("vec2 swap = ({}, {})\n", w.x, w.y);
|
||
print("vec2 sum = {}\n", ffi_vec2_sum(v));
|
||
|
||
// ── Vec4f (16 bytes, HFA) ──────────────────────────────────────
|
||
f := ffi_vec4f_make(1.0, 2.0, 3.0, 4.0);
|
||
print("vec4f make = ({}, {}, {}, {})\n", f.x, f.y, f.z, f.w);
|
||
g := ffi_vec4f_reverse(f);
|
||
print("vec4f rev = ({}, {}, {}, {})\n", g.x, g.y, g.z, g.w);
|
||
print("vec4f sum = {}\n", ffi_vec4f_sum(f));
|
||
|
||
// ── Pair64 (16 bytes, 2×s64 — [2 x i64] coercion path) ─────────
|
||
p := ffi_pair64_make(100, 200);
|
||
print("pair64 make = ({}, {})\n", p.a, p.b);
|
||
pp := ffi_pair64_swap(p);
|
||
print("pair64 swap = ({}, {})\n", pp.a, pp.b);
|
||
print("pair64 sum = {}\n", ffi_pair64_sum(p));
|
||
|
||
// ── Quad32 (16 bytes, 4×s32 — same coercion path as Pair64) ────
|
||
q := ffi_quad32_make(10, 20, 30, 40);
|
||
print("quad32 make = ({}, {}, {}, {})\n", q.a, q.b, q.c, q.d);
|
||
r := ffi_quad32_reverse(q);
|
||
print("quad32 rev = ({}, {}, {}, {})\n", r.a, r.b, r.c, r.d);
|
||
print("quad32 sum = {}\n", ffi_quad32_sum(q));
|
||
|
||
0
|
||
}
|