Files
sx/issues/0042-const-decl-type-aliases-not-resolved-as-identifier.md
agra 6fdfe8d073 issues: mark 0041, 0042, 0043, 0047 FIXED
Triage pass: every issue file in `issues/` was re-verified against
HEAD. Three (0041, 0042, 0043) reproduce no longer — they were
silently fixed by adjacent work since the issue was filed. 0047
landed in the previous commit. All four header sections now lead
with **FIXED** + a one-line locator so the next reader doesn't
re-investigate.

After this, `issues/` is the actual open-issue list:

| Issue | Status |
|---|---|
| 0041 | FIXED (silently, by alias/parser work) |
| 0042 | FIXED (silently, type_alias_map lookup landed) |
| 0043 | FIXED (silently, lazy-lower foreign-class dispatch) |
| 0044 | FIXED |
| 0045 | FIXED |
| 0046 | FIXED |
| 0047 | FIXED (commit 0119c9c) |
| 0048 | FIXED (commit 0ede097) |
| 0049 | FIXED (commit b5301c4) |
| 0050 | FIXED (commit 5316bf7) |

No open issues remain. The files stay in tree as a record; new
issues take the next free number (0051).
2026-05-28 08:16:06 +03:00

4.9 KiB

issue-0042 — Const-decl type aliases (MyInt :: s32;) silently return .s64 from size_of / align_of

FIXED. MyInt :: s32; 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 .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, in resolveTypeArg (line ~7132). The .identifier branch 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) and findByName (registered named types), but never consults self.type_alias_map — which is where the const-decl alias registration in lower.zig:425 puts entries. The neighbouring .type_expr branch (line ~7143) DOES check type_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 .identifier AST node is what parsePrimary emits for non-keyword names; .type_expr is what it emits for built-in primitive names recognised by Type.fromName (s32, u8, etc.) and for the f32/f64/Type keywords. User-defined alias names like MyInt and Ptr flow through .identifier.

Likely fix: mirror the type_alias_map.get lookup in the .identifier branch — 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:

  1. Add the repro above as examples/issue-0042.sx.
  2. bash tests/run_examples.sh --update to capture expected output (alias: 4, not alias: 8).
  3. Make sure existing snapshots that test type aliases (search examples/ for :: patterns followed by size_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

  1. Land 0042's .identifier alias-map lookup.
  2. Resume 0041 from the test step — re-run examples/issue-0041.sx and verify size_of(Maybe) = 2, size_of(Arr) = 3, etc.
  3. Regenerate snapshots and proceed with the 0041 finishing steps (50-smoke, rename, etc.).