fix(0127): namespaced generic calls type their result from the call's bindings
The plan producer's namespace-fn arms returned the declared return type
without checking type_params, so a qualified generic call's result
carried the unbound T stub: print boxed it as 'T{}', and a non-s64
binding failed LLVM verification (pack monomorphized for the stub,
call returning double). Both fn_ast_map-backed arms now classify
generic callees as generic_fn and infer the return through
inferGenericReturnType, mirroring the bare-identifier path.
This commit is contained in:
13
examples/0213-generics-namespaced-call-result.sx
Normal file
13
examples/0213-generics-namespaced-call-result.sx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// A NAMESPACED call to a generic free function types its result from the
|
||||||
|
// call's inferred bindings — not the unbound `T` stub (issue 0127:
|
||||||
|
// `m.pick(3, 9)` boxed as `T{}` while the flat spelling printed `9`).
|
||||||
|
#import "modules/std.sx";
|
||||||
|
m :: #import "0213-generics-namespaced-call-result/m.sx";
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
print("{}\n", m.pick(3, 9)); // s64 binding
|
||||||
|
print("{}\n", m.pick(1.5, 0.25)); // f64 binding
|
||||||
|
v := m.double(21);
|
||||||
|
w : s64 = v + 0; // the concrete type flows onward
|
||||||
|
print("{}\n", w);
|
||||||
|
}
|
||||||
9
examples/0213-generics-namespaced-call-result/m.sx
Normal file
9
examples/0213-generics-namespaced-call-result/m.sx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
pick :: (a: $T, b: T) -> T {
|
||||||
|
if a > b then a else b
|
||||||
|
}
|
||||||
|
|
||||||
|
double :: (x: $T) -> T {
|
||||||
|
x + x
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
9
|
||||||
|
1.500000
|
||||||
|
42
|
||||||
@@ -1,4 +1,23 @@
|
|||||||
# 0127: namespaced generic call's result mis-types as the unbound `T` stub
|
# 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-s64
|
||||||
|
> 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` (s64 + 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
|
## Symptom
|
||||||
|
|
||||||
|
|||||||
@@ -437,6 +437,15 @@ pub const CallResolver = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (self.l.program_index.fn_ast_map.get(qualified)) |qfd| {
|
if (self.l.program_index.fn_ast_map.get(qualified)) |qfd| {
|
||||||
|
// Generic callee: the declared return type is the unbound
|
||||||
|
// `T` stub — infer through the call's bindings, exactly
|
||||||
|
// like the bare-identifier path above.
|
||||||
|
if (qfd.type_params.len > 0) return .{
|
||||||
|
.kind = .generic_fn,
|
||||||
|
.return_type = self.l.genericResolver().inferGenericReturnType(qfd, c),
|
||||||
|
.target = .{ .named = qualified },
|
||||||
|
.expands_defaults = defaultsFor(qfd, c.args.len),
|
||||||
|
};
|
||||||
return .{
|
return .{
|
||||||
.kind = .namespace_fn,
|
.kind = .namespace_fn,
|
||||||
.return_type = if (qfd.return_type) |rt| self.l.resolveTypeInSource(self.l.program_index.qualified_fn_source.get(qualified), rt) else .void,
|
.return_type = if (qfd.return_type) |rt| self.l.resolveTypeInSource(self.l.program_index.qualified_fn_source.get(qualified), rt) else .void,
|
||||||
@@ -447,6 +456,12 @@ pub const CallResolver = struct {
|
|||||||
// Namespace aliases sometimes register the function under its
|
// Namespace aliases sometimes register the function under its
|
||||||
// bare name (matches `lowerCall`'s effective-name resolution).
|
// bare name (matches `lowerCall`'s effective-name resolution).
|
||||||
if (self.l.program_index.fn_ast_map.get(cfa.field)) |bfd| {
|
if (self.l.program_index.fn_ast_map.get(cfa.field)) |bfd| {
|
||||||
|
if (bfd.type_params.len > 0) return .{
|
||||||
|
.kind = .generic_fn,
|
||||||
|
.return_type = self.l.genericResolver().inferGenericReturnType(bfd, c),
|
||||||
|
.target = .{ .named = cfa.field },
|
||||||
|
.expands_defaults = defaultsFor(bfd, c.args.len),
|
||||||
|
};
|
||||||
return .{
|
return .{
|
||||||
.kind = .namespace_fn,
|
.kind = .namespace_fn,
|
||||||
.return_type = if (bfd.return_type) |rt| self.l.resolveTypeInSource(self.l.program_index.qualified_fn_source.get(qualified), rt) else .void,
|
.return_type = if (bfd.return_type) |rt| self.l.resolveTypeInSource(self.l.program_index.qualified_fn_source.get(qualified), rt) else .void,
|
||||||
|
|||||||
Reference in New Issue
Block a user