# 0121 — aliasing a comptime-pack fn (`..$args`): "unresolved '<alias>'"

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

```sx
#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`), `extern` decl aliases
(`memcpy`).
