Files
sx/issues/0042-const-decl-type-aliases-not-resolved-as-identifier.md
agra c21b683b08 docs(issues): mark 17 already-fixed issues RESOLVED with verified banners
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.
2026-06-21 09:25:52 +03:00

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 — resolveNominalLeafselectNominalLeaf (src/ir/lower/decl.zig) returns .resolved with the alias's real TypeId from program_index.type_aliases_by_source (own-author const_decl branch ~L1820 / unwired branch ~L1801); aliases are registered via putTypeAlias (decl.zig:560). The old resolveTypeArg .identifier arm in lower.zig is gone — the source-keyed selectNominalLeaf superseded it. Covering regression test: examples/0116-types-type-alias-size-align.sx (alias, chain, and struct-name alias through size_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, 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 .i64;
},

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 (i32, 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 .i64;
},

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 :: 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

  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.).