Files
sx/examples/1210-ffi-02-small-struct.sx
agra bdd0e96d78 feat(lang): block value requires no trailing ; (Rust-style)
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).
2026-06-02 09:23:50 +03:00

76 lines
3.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}