Files
sx/issues/0127-namespaced-generic-call-result-unbound-stub.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

3.0 KiB

RESOLVED — 0127: namespaced generic call's result mis-types as the unbound T stub

RESOLVED (2026-06-12). Root cause: the call-PLAN producer's namespace-fn arms (src/ir/calls.zig, the fn_ast_map-backed qualified and bare-name fallbacks) returned the DECLARED return type — T, resolving to the unbound stub — without checking type_params, while the bare-identifier path routes generics through inferGenericReturnType. Lowering dispatched the right mono (the value was correct); only the planned result type was wrong, so pack-fn callers (print's Any boxing) mis-tagged it — and a non-i64 binding (f64) failed LLVM verification outright, the pack being monomorphized for the stub while the call returned double. Fix: both arms now classify a type_params.len > 0 callee as .generic_fn and infer the return type through the call's bindings, mirroring the flat path. Regression test: examples/0213-generics-namespaced-call-result.sx (i64 + f64 bindings via print, concrete type flowing into arithmetic; pre-fix: T{} boxing / LLVM verification failure — both demonstrated). Gates: zig build test 426/426, suite 595/595, distribution repo 14/14.

Symptom

A NAMESPACED call to a generic free function returns a value typed as the unbound T empty-struct stub instead of the bound concrete type.

  • Observed: print("{}\n", m.pick(3, 9)) prints T{} — the Any boxing tags the result with the unbound T stub type.
  • Expected: prints 9.

The same call through a FLAT import (pick(3, 9)) prints 9 — only the namespaced spelling (field_access callee whose object is a namespace alias) mis-types the result.

Reproduction

m.sx:

#import "modules/std.sx";

pick :: (a: $T, b: T) -> T {
    if a > b then a else b
}

main.sx:

#import "modules/std.sx";
m :: #import "m.sx";

main :: () {
    print("{}\n", m.pick(3, 9));
}

Observed at master 309f48e: prints T{}. The flat-import spelling of the same call prints 9.

Investigation prompt

Suspected area: the namespaced/qualified dispatch leg in src/ir/lower/call.zig lowerCall (the qualified fn_ast_map paths) and/or the generic return-type inference not being consulted for the namespaced callee shape. The plain-identifier path routes the call's result typing through buildTypeBindings (the one binding builder — see a47ea14's note); the namespaced path appears to stamp the declared return type (T) without substituting the call's inferred bindings, so the result carries the unbound-T stub struct into Any boxing.

Fix: make the namespaced generic call derive its result type from the call's inferred $T → concrete bindings exactly like the flat path. Check whether the value itself is computed correctly (the mono body) and only the recorded result TYPE is wrong, or whether dispatch skipped monomorphization entirely.

Verification: the repro prints 9; the flat spelling still prints 9; zig build && zig build test, bash tests/run_examples.sh green; pin the repro as a generics example (next free 02xx number).