Files
sx/examples/0604-comptime-typed-store-widths.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

191 lines
5.1 KiB
Plaintext

// Lock down the interp's raw-pointer store width per primitive type.
//
// Each helper allocates a 32-byte buffer through `context.allocator`,
// fills it with a sentinel byte (0xAA), writes ONE typed value at
// offset 8, then sums every byte back. A correctly-sized store touches
// exactly `sizeof(T)` bytes, so the sum equals
// 31 * 0xAA + sum-of-bytes-in-the-written-value.
// A wrong width (e.g. an 8-byte store at a 1-byte slot) clobbers
// neighbors with zeros and the sum drops.
//
// Each test computes its expected sum at COMPTIME (the value is baked
// into a `#run` constant — the interp's `storeAtRawPtr` runs). The
// runtime program prints the same checksum computed by codegen
// (LLVM-emitted typed stores). The two MUST match — that's the
// regression assertion.
//
// To pin the test: every helper returns its checksum; main prints
// "ok" iff every comptime-baked checksum equals the runtime-recomputed
// one. Failure prints which width diverged.
#import "modules/std.sx";
SENTINEL :u8: 0xAA; // 170 — neighbor pattern
BUF_SIZE :s64: 32;
TARGET :s64: 8; // offset where the typed store lands
// ── per-width helpers ───────────────────────────────────────────────
fill :: (buf: [*]u8) {
i : s64 = 0;
while i < BUF_SIZE { buf[i] = SENTINEL; i += 1; }
}
sum_bytes :: (buf: [*]u8) -> s64 {
s : s64 = 0;
i : s64 = 0;
while i < BUF_SIZE { s += xx buf[i]; i += 1; }
s
}
run_u8 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *u8 = xx @buf[TARGET];
p.* = 0x42;
s := sum_bytes(buf);
free(xx buf);
s
}
run_u16 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *u16 = xx @buf[TARGET];
p.* = 0x0102;
s := sum_bytes(buf);
free(xx buf);
s
}
run_u32 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *u32 = xx @buf[TARGET];
p.* = 0x01020304;
s := sum_bytes(buf);
free(xx buf);
s
}
run_u64 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *u64 = xx @buf[TARGET];
p.* = 0x0102030405060708;
s := sum_bytes(buf);
free(xx buf);
s
}
run_s8 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *s8 = xx @buf[TARGET];
p.* = 0x42;
s := sum_bytes(buf);
free(xx buf);
s
}
run_s16 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *s16 = xx @buf[TARGET];
p.* = 0x0102;
s := sum_bytes(buf);
free(xx buf);
s
}
run_s32 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *s32 = xx @buf[TARGET];
p.* = 0x01020304;
s := sum_bytes(buf);
free(xx buf);
s
}
run_s64 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *s64 = xx @buf[TARGET];
p.* = 0x0102030405060708;
s := sum_bytes(buf);
free(xx buf);
s
}
run_bool :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *bool = xx @buf[TARGET];
p.* = true;
s := sum_bytes(buf);
free(xx buf);
s
}
run_f32 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *f32 = xx @buf[TARGET];
p.* = 1.0;
s := sum_bytes(buf);
free(xx buf);
s
}
run_f64 :: () -> s64 {
buf : [*]u8 = xx malloc(BUF_SIZE);
fill(buf);
p : *f64 = xx @buf[TARGET];
p.* = 1.0;
s := sum_bytes(buf);
free(xx buf);
s
}
// ── comptime-baked expected checksums ───────────────────────────────
// `#run` evaluates each helper via the interp, so its
// `storeAtRawPtr(addr, val, val_ty)` honors the declared width.
EXP_U8 :: #run run_u8();
EXP_U16 :: #run run_u16();
EXP_U32 :: #run run_u32();
EXP_U64 :: #run run_u64();
EXP_S8 :: #run run_s8();
EXP_S16 :: #run run_s16();
EXP_S32 :: #run run_s32();
EXP_S64 :: #run run_s64();
EXP_BOOL :: #run run_bool();
EXP_F32 :: #run run_f32();
EXP_F64 :: #run run_f64();
// ── runtime comparison ──────────────────────────────────────────────
check :: (label: string, got: s64, want: s64) -> bool {
if got == want { return true; }
print("FAIL {}: comptime={} runtime={}\n", label, want, got);
false
}
main :: () -> s32 {
ok := true;
if !check("u8", run_u8(), EXP_U8) { ok = false; }
if !check("u16", run_u16(), EXP_U16) { ok = false; }
if !check("u32", run_u32(), EXP_U32) { ok = false; }
if !check("u64", run_u64(), EXP_U64) { ok = false; }
if !check("s8", run_s8(), EXP_S8) { ok = false; }
if !check("s16", run_s16(), EXP_S16) { ok = false; }
if !check("s32", run_s32(), EXP_S32) { ok = false; }
if !check("s64", run_s64(), EXP_S64) { ok = false; }
if !check("bool", run_bool(), EXP_BOOL) { ok = false; }
if !check("f32", run_f32(), EXP_F32) { ok = false; }
if !check("f64", run_f64(), EXP_F64) { ok = false; }
if ok { print("ok\n"); return 0; }
return 1;
}