The session-long set of changes that lay the groundwork for the
Jai-literal implicit-Context-parameter refactor. Lots of accumulated
work; the new arrival is the implicit-ctx foundation (steps 1+2 of
the plan in current/CHECKPOINT-MEM.md):
Step 1 — `CAllocator :: struct {}` stateless allocator in
library/modules/allocators.sx, delegating directly to
libc_malloc/libc_free. `ConstantValue` in src/ir/inst.zig gains a
`func_ref: FuncId` leaf so nested aggregates can carry function
pointers (the inline Allocator value's fn-ptr fields). Switch
sites updated in emit_llvm.zig, print.zig, interp.zig.
Step 2 — `emitDefaultContextGlobal` in src/ir/lower.zig synthesises
a static `__sx_default_context` global with a nested-aggregate
init_val pointing at the CAllocator → Allocator thunks. The
second-pass `initVtableGlobals` in emit_llvm.zig is generalised
to handle `.aggregate` init_vals (re-emits after func_map is
populated so func_ref leaves resolve to real symbols).
Also folded in from earlier work this session:
- Phase 1.1: `xx value` heap-copy in `buildProtocolValue` routes
through `context.allocator` via the new `allocViaContext` helper.
- interp.zig: `marshalForeignArg` double-offset bug fixed —
`heapSlice` already adds `hp.offset` to the slice ptr, so the
extra `+ hp.offset` was scribbling memcpy/memset into adjacent
heap state, corrupting `heap.items[0]`. Symptom: `build_format`
at comptime produced zero bytes, all `print` calls failed.
- Lazy lowering: `lazyLowerFunction` now declares foreign-body
functions as extern stubs in the local (comptime) module so
cross-module foreign calls resolve.
- Allocator API: all stdlib allocators on one-line `init() -> *T`
(CAllocator/GPA: libc-backed; Arena/TrackingAllocator: parent-
backed; BufAlloc: embeds state at head of user buffer).
- issues 0038 (transitive #import), 0039 (chess + stdlib migration
fallout), 0040 (generic struct method dot-dispatch), 0041
(pointer types as type-arg), 0042 (alias name resolution) — all
fixed; regression tests in examples/.
- Diagnostic: `emitError` now embeds the lowering's
`current_source_file` and enclosing function in the literal
message; SX_TRACE_UNRESOLVED=1 dumps a Zig stack trace at the
emit site so misattributed spans can't hide where the failure
is.
- tools/verify-step.sh (all-platforms gate) and tools/scratch.sh
(interp/codegen parity tester) added.
Test suite: 152 example tests pass; chess builds + screenshots on
macOS / iOS sim / Android.
4.6 KiB
issue-0042 — Const-decl type aliases (MyInt :: s32;) silently return .s64 from size_of / align_of
Symptom
A type alias declared via Foo :: SomeType; is registered in the
lowering's type_alias_map but is never consulted when the alias
name is later used as a type argument to size_of / align_of. The
fallback returns .s64 (8 bytes) — which coincidentally produces a
correct result for any alias whose underlying type is 8 bytes
(*T, f64, function pointers, s64, u64), silently masking the
bug for years.
Observed:
size_of(s32) = 4 ← direct, correct
size_of(MyInt) = 8 ← via alias, WRONG (expected 4)
Where MyInt :: s32;.
Reproduction
#import "modules/std.sx";
MyInt :: s32;
main :: () -> s32 {
print("direct: {}\n", size_of(s32)); // 4
print("alias: {}\n", size_of(MyInt)); // 8 — should be 4
0;
}
./zig-out/bin/sx run against unmodified master prints:
direct: 4
alias: 8
Why this surfaces now
issue-0041 work extends the const-decl alias path to register
pointer, optional, array, slice, many-pointer, and function-type
aliases (Ptr :: *u8;, Maybe :: ?u8;, Arr :: [3]u8;,
Cb :: (s32) -> s32;). Every one of those aliases ends up in
type_alias_map, then size_of(<alias>) falls through the same
.identifier branch that ignores the map — returning .s64 (8).
For pointer and function-type aliases this is coincidentally right
(8 bytes). For optional, array, etc. it produces silently-wrong
sizes (size_of(Maybe) = 8 instead of 2;
size_of(Arr) = 8 instead of 3).
The issue-0041 work cannot land without this being fixed — the test snapshots would pin in the wrong values and the new feature would ship subtly broken.
Investigation prompt
The bug lives in
src/ir/lower.zig, inresolveTypeArg(line ~7132). The.identifierbranch looks like:.identifier => |id| { if (self.type_bindings) |tb| { if (tb.get(id.name)) |ty| return ty; } const name_id = self.module.types.internString(id.name); return self.module.types.findByName(name_id) orelse .s64; },It checks
type_bindings(generic-monomorphization) andfindByName(registered named types), but never consultsself.type_alias_map— which is where the const-decl alias registration inlower.zig:425puts entries. The neighbouring.type_exprbranch (line ~7143) DOES checktype_alias_map:.type_expr => |te| { if (self.type_alias_map.get(te.name)) |alias_ty| return alias_ty; return type_bridge.resolveAstType(node, &self.module.types); },Why two branches: an
.identifierAST node is what parsePrimary emits for non-keyword names;.type_expris what it emits for built-in primitive names recognised byType.fromName(s32,u8, etc.) and for thef32/f64/Typekeywords. User-defined alias names likeMyIntandPtrflow through.identifier.Likely fix: mirror the
type_alias_map.getlookup in the.identifierbranch — try alias map first (or before/after findByName, whichever is the established precedence elsewhere)..identifier => |id| { if (self.type_bindings) |tb| { if (tb.get(id.name)) |ty| return ty; } if (self.type_alias_map.get(id.name)) |alias_ty| return alias_ty; const name_id = self.module.types.internString(id.name); return self.module.types.findByName(name_id) orelse .s64; },Verification:
- Add the repro above as
examples/issue-0042.sx.bash tests/run_examples.sh --updateto capture expected output (alias: 4, notalias: 8).- Make sure existing snapshots that test type aliases (search
examples/for::patterns followed bysize_of) don't change in unexpected ways.Possible adjacency: the issue may extend to
align_of(likely same call path) and to type-alias chains (A :: s32; B :: A;— does B resolve through A's alias entry?). Worth pinning down with a test once the primary fix lands.
Plan-level impact
Blocks issue-0041 (compound-type-as-expression). Once 0042 is fixed, 0041 work can resume from the testing phase (the parser and lowering edits for 0041 are already in place; only the alias lookup is broken).
Suggested fix order
- Land 0042's
.identifieralias-map lookup. - Resume 0041 from the test step — re-run
examples/issue-0041.sxand verifysize_of(Maybe) = 2,size_of(Arr) = 3, etc. - Regenerate snapshots and proceed with the 0041 finishing steps (50-smoke, rename, etc.).