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.
allocators/fs/process/socket/log/trace/test move under modules/std/
(allocators.sx becomes std/mem.sx; the Allocator protocol moves into
the std.sx prelude, impls stay in mem.sx). New std/xml.sx holds
xml_escape as xml.escape. std.sx gains the carried namespace tail —
flat-importing std.sx now also provides mem./xml./log. — with the
remaining modules (fs/process/socket/json/cli/hash/test) deferred from
the tail until the global last-wins maps are fully own-wins (pulling
them into every closure collides bare names corpus-wide; they stay
direct imports: modules/std/fs.sx etc.). log.sx's internal emit
renamed log_emit (it clobbered consumer fns named emit program-wide).
bundle.sx uses xml.escape via the carried alias. Consumer import paths
swept mechanically; .ir snapshots recaptured for the larger std
closure. m3te + game build unchanged.
Incomplete WIP from a worker killed at the 55-min wall (large blast radius:
core source-pin + ~8 example migrations + ~10 library module migrations).
Committed so the resumed session continues on a clean tree. May not build.
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).
The chess panel-text regression (text vanished after the first move on
macOS) had a single root cause: GlyphCache's entries List, hash table,
and shaped_buf grew through `context.allocator` — which during render
is the per-frame arena. On the next arena reset the backing died, and
subsequent glyph lookups read garbage / wrote into freshly-allocated
view-tree memory.
Fix is shaped as the user proposed: `List(T)`'s mutations take an
optional trailing `alloc: Allocator = context.allocator` argument. No
allocator stored on the container, no init ceremony, every existing
`list.append(item)` callsite keeps working unchanged. Long-lived
owners now write `list.append(item, self.parent_allocator)` and the
arena-leak bug becomes impossible to write accidentally.
Default-arg substitution previously only fired for identifier callees
(`expandCallDefaults` at lower.zig:7978). Extended to the generic
struct-method dispatch path (`list.append(...)` lands here) via a new
`appendDefaultArgs` helper that lowers fd.params[i].default_expr in
the caller's scope and appends to the lowered args slice.
Long-lived owners updated to capture `parent_allocator: Allocator` at
init and use it for every internal growth:
- GlyphCache (the chess bug) — entries, shaped_buf, hash_keys,
hash_vals, atlas bitmap.
- DockInteraction — drops the existing `push Context` workaround in
`ensure_capacity` for the explicit-arg form.
- StateStore — entries list + per-entry data buffer.
- Gles3Gpu, MetalGPU — shaders, buffers, textures (atlas-grow during
render would otherwise leak resources into the frame arena).
Also kept: an operator-precedence fix in pipeline.sx
(`(self.frame_index & 1) == 0` instead of
`self.frame_index & 1 == 0`, which parses as
`self.frame_index & (1 == 0)` = always 0). That was a stealth
single-arena-only bug that masked the GlyphCache one for a long time.
Docs:
- specs.md §11 documents `param: T = expr` default parameter values.
The parser already supported it — formalised in the spec now.
- current/CHECKPOINT-MEM.md logs the change.
- CLAUDE.md REJECTED PATTERNS gains a "Long-lived containers growing
through context.allocator" section with the `parent_allocator`
capture template and the list of existing examples to mirror.
155/155 example tests pass — zero-diff against snapshots since every
existing callsite still resolves to `context.allocator`.
20 files (~3,830 lines): view protocol, layout, renderer, glyph cache,
fonts, gestures, animation, scroll, stacks, modifiers, etc.
Internal imports rewritten from "ui/..." to "modules/ui/...".
Consumers now `#import "modules/ui"` from any project; no symlink
hacks needed. Verified by compiling game/main.sx without its local
ui/ — resolves via the Phase 6 stdlib fallback.