Files
sx/issues/0121-pack-fn-alias-unresolved.md
agra d8076b9333 lang: rename signed integer types sN -> iN
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.
2026-06-12 09:31:53 +03:00

5.2 KiB

0121 — aliasing a comptime-pack fn (..$args): "unresolved ''"

RESOLVED (2026-06-11, same session — Agra-directed). The symptom was broader than filed: RENAMED fn aliases failed for EVERY fn kind (plain helper2 :: r.helper; too) — the "plain fns verified working" claim below was a same-name confound (same-name re-exports resolve through the name-keyed global fn_ast_map, no alias mechanism involved; my_pack :: r.my_pack; already worked for packs too). Fix: fn aliases register at SCAN time — scanDecls' const-decl arm follows ident-/ns.X-RHS alias chains via aliasedFnDecl (nominal.zig; shares 0120's hop walk, now extracted as followAliasChain) and, when the chain terminates at a fn decl, registers the ALIAS name in fn_ast_map (absent-only — a real same-name fn keeps its slot). Every dispatch path reads that map (early pack/comptime/generic, plain lazy-lower, plan-side typing), so the alias dispatches exactly like the target with no per-path changes. Verified matrix: same-file pack alias (the repro), renamed plain / generic / pack through a facade, and my_print :: s.print; / my_format :: s.format; over std's real pack fns. Regression: examples/0546-packs-fn-alias.sx (+ -rich.sx companion). Gates: zig build test 0, suite 588/588.

Symptom

A const alias of a function whose signature carries a comptime pack (..$args) is not callable — every call through the alias fails with unresolved '<alias>'. All three alias shapes fail identically:

  • same-file bare: sum_alias :: pack_sum;unresolved 'sum_alias'
  • bare RHS over a flat import: my_print :: print; (std's print) → unresolved 'my_print'
  • namespace RHS: my_print :: s.print; with s :: #import "modules/std.sx";unresolved 'my_print'

Contrast (all verified working): plain concrete fns (helper :: r.helper;), runtime-generic fns (first_of :: r.first_of; with (xs: []$T) -> T), and — since 0120 — generic struct heads (List :: list.List;). Only the comptime-pack shape misses.

Expected: the alias dispatches exactly like the target — my_print("x {}\n", 1) behaves as print("x {}\n", 1). (If fn aliasing of pack fns is NOT meant to be promised, the hypothesis is wrong and the decl or the call should get a clean tailored diagnostic instead — but the std.sx-as-pure-re-exports restructure wants print :: fmt.print; to work, so support is the desirable outcome. Confirm with Agra only if support turns out prohibitively deep.)

Reproduction

#import "modules/std.sx";

pack_sum :: (..$args) -> i64 {
    args[0] + args[1]
}
sum_alias :: pack_sum;

main :: () {
    print("{}\n", sum_alias(3, 4));   // error: unresolved 'sum_alias'
}

Direct pack_sum(3, 4) works; only the aliased spelling fails.

Investigation prompt

Comptime-pack calls (..$args — NOT slice-variadics ..xs: []T) dispatch EARLY in call lowering, keyed on the callee NAME against the pack-fn template registry (the fn_ast_map entry whose FnDecl has a comptime pack param; the early-dispatch gate lives in the call path — src/ir/lower/call.zig / src/ir/packs.zig, grep for the pack-param detection on the callee). An alias name has no fn_ast_map entry, so the early pack dispatch misses; no later stage handles pack fns (they cannot lower as ordinary declared functions — each call site expands with its own bound pack), so the call falls through to the generic unresolved '<name>'.

The fix likely: where the early pack dispatch resolves the callee name, on a miss follow const-ALIAS decls to their target FnDecl and dispatch with the TARGET's fd under the alias's call node. Issue 0120's fix added exactly this follow for generic STRUCT heads — aliasedStructTemplate in src/ir/lower/nominal.zig (singleVisibleAuthor + hop-by-hop followToTemplate, each hop resolved from the ALIAS AUTHOR's source, namespaceAliasVerdictFrom for ns.X RHS, depth-capped). Mirror that shape for fn targets — a followToFnDecl sibling reusing singleVisibleAuthor (consider extracting the shared walk) — and route the early pack dispatch through it. Mind: own-wins / single-flat collision semantics must match 0120's (≥2 flat alias authors → loud, no silent pick), and the ufcs-alias map (name :: ufcs target;) is a DIFFERENT mechanism (ufcs_alias_map) — don't conflate.

Verification:

  1. The repro prints 7, exit 0.
  2. Matrix: same-file bare alias, bare RHS over a flat import (my_print :: print;), namespace RHS (my_print :: s.print; / my_format :: s.format; — formats AND returns a value), and a consumer one flat hop from the aliasing facade.
  3. Plain-fn and generic-fn aliases unchanged (examples 0211 family).
  4. bash tests/run_examples.sh — 587/587 baseline must hold; pin the repro as a regression example per CLAUDE.md.

Context: BLOCKS the std.sx-as-pure-re-exports restructure — print / format are the prelude's most-used names and are exactly this shape (($fmt: string, ..$args)). Generic struct heads (List) were unblocked by 0120; this is the remaining known gap. Still unprobed for the restructure (next session, after this fix): protocol aliases (Allocator, parameterized Into), #builtin decl aliases (size_of, out, string :: []u8), #foreign decl aliases (memcpy).