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.
101 lines
4.8 KiB
Markdown
101 lines
4.8 KiB
Markdown
# 0119 — UFCS dot-call on a GENERIC free function: "unresolved '<name>'"
|
|
|
|
> **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 '<name>'`. 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.
|