Each banner was re-verified against the current binary (repro now behaves correctly) and cites the actual fix location in current src/** plus the covering regression example. Closes the stale-but-fixed backlog: 0019, 0042-0056, 0131. No compiler change.
5.8 KiB
issue-0042 — Const-decl type aliases (MyInt :: i32;) silently return .i64 from size_of / align_of
RESOLVED. Root cause: the bare-name type-arg resolver consulted only
findByName, falling back to.i64(8 bytes) for const-decl aliases (MyInt :: i32;) recorded in the alias map — silently mis-sizing any non-8-byte alias. Fix: the nominal-leaf resolver now consults the alias maps before falling back —resolveNominalLeaf→selectNominalLeaf(src/ir/lower/decl.zig) returns.resolvedwith the alias's realTypeIdfromprogram_index.type_aliases_by_source(own-author const_decl branch ~L1820 / unwired branch ~L1801); aliases are registered viaputTypeAlias(decl.zig:560). The oldresolveTypeArg.identifierarm inlower.zigis gone — the source-keyedselectNominalLeafsuperseded it. Covering regression test:examples/0116-types-type-alias-size-align.sx(alias, chain, and struct-name alias throughsize_of/align_of).
FIXED. MyInt :: i32; size_of(MyInt) now returns 4
correctly. The resolveTypeArg .identifier branch consults
type_alias_map before falling through. The fix landed
alongside the broader alias-resolution work tracked in
CHECKPOINT.md (Session 63's type_bridge alias-resolution
extension); no specific commit isolates this issue.
Below preserved as a record of the original problem.
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 .i64 (8 bytes) — which coincidentally produces a
correct result for any alias whose underlying type is 8 bytes
(*T, f64, function pointers, i64, u64), silently masking the
bug for years.
Observed:
size_of(i32) = 4 ← direct, correct
size_of(MyInt) = 8 ← via alias, WRONG (expected 4)
Where MyInt :: i32;.
Reproduction
#import "modules/std.sx";
MyInt :: i32;
main :: () -> i32 {
print("direct: {}\n", size_of(i32)); // 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 :: (i32) -> i32;). 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 .i64 (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 .i64; },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(i32,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 .i64; },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 :: i32; 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.).