issue 0151 RESOLVED: infer generic $T through generic-struct / pointer / UFCS-pack params

The generic-inference engine could not bind a $T from a generic-struct
argument head. Four gaps, all on the inference + UFCS dispatch path:

- extractTypeParam / matchTypeParam(Static) gained a parameterized_type_expr
  arm: recover the arg instance's recorded per-param bindings
  (struct_instance_bindings + the template's ordered type_params via
  struct_instance_author) and recurse positionally, so $T binds from
  Box($T) <=> Box(i64) like it does from []$T <=> []i64. This also fixes
  the pointer case — *Box($T) recurses into its Box($T) pointee.
- The pointer_type_expr arm now falls through to match the pointee against a
  non-pointer arg (auto-address-of: a *Box($T) param accepts a by-value
  Box($T), e.g. the UFCS receiver b.m()).
- ExprTyper.inferType gained a .lambda arm building the closure type from the
  lambda's annotations, so the UFCS binder (which types args from the raw AST
  before they are lowered) can bind a Closure(..) -> $R from the worker's
  declared return type.
- A pack UFCS target (worker: Closure(..) -> $R, ..$args) now routes through
  the same lowerPackFnCall the direct call uses, with the receiver spliced in
  as args[0] (lowerPackFnCall reads only call_node.args, never the callee).

Regression tests: examples/0214 (direct + UFCS closure-return pack) and
examples/0215 (by-value / pointer / multi-param / nested / UFCS-auto-ref
generic-struct-head inference). Suite green 728/0.
This commit is contained in:
agra
2026-06-21 05:25:39 +03:00
parent 0ab26c8a40
commit 362674f04d
13 changed files with 190 additions and 24 deletions

View File

@@ -1033,6 +1033,25 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{fa.field});
return Ref.none;
}
// A pack ufcs target (`worker: Closure(..) -> $R, ..$args`):
// route through the SAME pack-call path the direct call uses,
// with the receiver spliced in as the first arg so the pack
// boundary, the `$R` closure-return binding, and the pack
// expansion all line up with `fd.params[0]` (issue 0151).
// `lowerPackFnCall` reads only `call_node.args` (never the
// callee), so a synthetic spliced-args call is sufficient.
if (ufcs_fd) |fd| {
if (isPackFn(fd)) {
// `lowerPackFnCall` only READS these nodes; the const-cast
// back to `*Node` (Call.args' element type) is sound.
var syn_args = std.ArrayList(*Node).empty;
defer syn_args.deinit(self.alloc);
syn_args.append(self.alloc, @constCast(effective_obj_node)) catch unreachable;
for (c.args) |a| syn_args.append(self.alloc, a) catch unreachable;
const syn_call = ast.Call{ .callee = c.callee, .args = syn_args.items };
return self.lowerPackFnCall(fd, &syn_call);
}
}
// Generic ufcs target: monomorphize with the receiver's AST
// node prepended so bindings align with fd.params[0].
if (ufcs_fd) |fd| {