# 0119 — UFCS dot-call on a GENERIC free function: "unresolved ''" > **RESOLVED** (2026-06-11, same session — Agra language ruling + the > opt-in implementation). Final model: free-function dot-calls are > OPT-IN. `name :: ufcs (params) { body }` (new declaration form) and > `name :: ufcs target;` (alias) both opt in; a PLAIN fn never > dot-dispatches (tailored diagnostic steers to direct / `|>` / > marking it ufcs). Generic ufcs fns dispatch via dot with the > receiver participating in `$T` binding; a protocol-typed receiver > dispatches its own methods first and falls through to ufcs fns > (`context.allocator.create(Session)` works). Bonus root-cause fix: > plan-side `inferGenericReturnType` now delegates to the SAME > `buildTypeBindings` the monomorphizer uses, so structured generic > params (`[]$T`) type direct calls correctly too (was a `T{}` stub > through print's Any boxing — pre-existing). The previously-implicit > unannotated dot-dispatch was REMOVED (inverted vs the model); > in-tree reliance was 6 example files (audited; migrated to marked > form), zero in m3te/game. specs.md §UFCS rewritten around the > opt-in matrix. Regression: examples/0053-basic-ufcs-opt-in.sx + > 1166-diagnostics-ufcs-not-opted-in.sx; mem helpers marked ufcs > (0838 pins dot + pipe + direct). Suite 585/585. ## Symptom `obj.func(args)` where `func` is a generic free function (any `$T` in its signature — a `[]$T`/`*$T` param or a `$T: Type` value param) fails with `unresolved ''`. The same call spelled directly — `func(obj, args)` — compiles and runs correctly. Concrete (non-generic) free functions rewrite through UFCS fine. Observed (one probe, all three failures): - `xs.sum_all()` (concrete fn, slice receiver) → **works** - `xs.first_of()` (generic `[]$T` fn, slice receiver) → `unresolved 'first_of'` - `p.pick(i32)` (generic `$T: Type` fn, struct receiver) → `unresolved 'pick'` - `a.create(Session)` (generic fn, protocol-value receiver) → `unresolved 'create'` Expected: specs.md §UFCS promises the rewrite unconditionally ("When `object.func(args)` is encountered and `func` is not a field of `object`'s type, the compiler rewrites the call to `func(object, args)`"). A generic free function called via dot must monomorphize and dispatch exactly as the direct spelling does. Note: issue-0040 (fixed) covered generic STRUCT METHODS via dot — that is the method path, not the free-function UFCS rewrite. ## Reproduction ```sx #import "modules/std.sx"; first_of :: (xs: []$T) -> T { xs[0] } main :: () { arr := .[1, 2, 3]; xs : []i64 = arr; print("{}\n", first_of(xs)); // 1 — direct call works print("{}\n", xs.first_of()); // error: unresolved 'first_of' } ``` ## Investigation prompt The UFCS rewrite lives in the call-lowering path (`src/ir/calls.zig` / `src/ir/lower/call.zig` — the field-access-callee handling that falls back to "func is not a field → try `func(object, args)`"). The fallback resolves the bare name against DECLARED functions (`resolveFuncByName` / the lowered-function registry). A generic free function is never declared (it is a TEMPLATE in `fn_ast_map`, `fd.type_params.len > 0`, monomorphized per call shape) — so the lookup misses and the call is reported unresolved instead of routing through the generic machinery. The fix likely: in the UFCS fallback, when the bare name resolves to a `fn_ast_map` entry with `type_params.len > 0` (the same gate `declareFunction` uses), rewrite to the direct-call shape and route through the SAME generic-call path a direct `func(obj, args)` takes (`mangleGenericName` + binding inference from args + monomorphize). The direct spelling already works, so the machinery exists — the UFCS arm just never reaches it. Mind the resolution order: scope locals and protocol/struct methods must keep winning over a same-named free template (mirror the existing concrete-UFCS precedence), and visibility gating (import-graph) must apply to the template exactly as for concrete fns. Verification: 1. The repro above prints `1` twice, exit 0. 2. Matrix probe: generic-on-struct (`p.pick(i32)`), generic-on-slice (`xs.first_of()`), generic-on-protocol-value (`a.create(Session)` with `create :: (a: Allocator, $T: Type) -> *T`) all dispatch; concrete UFCS unchanged. 3. `bash tests/run_examples.sh` — 582/582 baseline must hold (UFCS-heavy suite: protocols, packs, List methods). 4. Pin the repro as a regression example per CLAUDE.md. Context: BLOCKS MEM Phase 2.2 — the plan's memory helpers are "free functions in mem.sx, UFCS-callable" with canonical call sites `context.allocator.create(Session)` / `slice.clone(context.allocator)` (plan Appendix A). The helpers themselves work via direct calls; the step is paused rather than shipping a direct-call-only API that the plan would immediately re-churn.