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

@@ -537,6 +537,10 @@ pub fn matchTypeParam(_: *Lowering, type_node: *const Node, tp_name: []const u8)
if (ct.return_type) |rt| if (matchTypeParamStatic(rt, tp_name)) break :blk true;
break :blk false;
},
.parameterized_type_expr => |pt| blk: {
for (pt.args) |a| if (matchTypeParamStatic(a, tp_name)) break :blk true;
break :blk false;
},
else => false,
};
}
@@ -555,6 +559,10 @@ pub fn matchTypeParamStatic(type_node: *const Node, tp_name: []const u8) bool {
if (ct.return_type) |rt| if (matchTypeParamStatic(rt, tp_name)) break :blk true;
break :blk false;
},
.parameterized_type_expr => |pt| blk: {
for (pt.args) |a| if (matchTypeParamStatic(a, tp_name)) break :blk true;
break :blk false;
},
else => false,
};
}
@@ -583,7 +591,11 @@ pub fn extractTypeParam(self: *Lowering, type_node: *const Node, arg_ty: TypeId,
const info = self.module.types.get(arg_ty);
break :blk switch (info) {
.pointer => |p| self.extractTypeParam(pt.pointee_type, p.pointee, tp_name),
else => null,
// Auto-address-of: a `*Box($T)` param accepts a by-value
// `Box($T)` arg (the UFCS receiver `b.m()` / a value passed to a
// pointer param). Match the pointee against the value arg so the
// type-var still binds (issue 0151).
else => self.extractTypeParam(pt.pointee_type, arg_ty, tp_name),
};
},
.many_pointer_type_expr => |mp| blk: {
@@ -628,6 +640,33 @@ pub fn extractTypeParam(self: *Lowering, type_node: *const Node, arg_ty: TypeId,
}
break :blk null;
},
.parameterized_type_expr => |pt| blk: {
// A generic-struct param head (`Box($T)`, also reached recursively
// for a pointer-wrapped `*Box($T)`): the arg is a monomorphized
// instance whose per-param bindings were recorded at instantiation
// (`struct_instance_bindings`). Recover the concrete type the i-th
// template param bound and recurse against the i-th param-head arg,
// so `$T` is inferred from `Box($T)` ⇔ `Box(i64)` exactly as it is
// from `[]$T` ⇔ `[]i64` (issue 0151).
if (arg_ty.isBuiltin()) break :blk null;
const info = self.module.types.get(arg_ty);
if (info != .@"struct") break :blk null;
const inst_name = self.module.types.getString(info.@"struct".name);
const binds = self.struct_instance_bindings.getPtr(inst_name) orelse break :blk null;
// The param head must name the same template the arg instance was
// stamped from, so the positional args line up with the params.
const tmpl_name = self.struct_instance_template.get(inst_name) orelse break :blk null;
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
if (!std.mem.eql(u8, base_name, tmpl_name)) break :blk null;
const author = self.struct_instance_author.get(inst_name) orelse break :blk null;
for (author.type_params, 0..) |atp, i| {
if (i >= pt.args.len) break;
if (atp.is_variadic) break; // type-pack params not inferred here
const concrete = binds.get(atp.name) orelse continue;
if (self.extractTypeParam(pt.args[i], concrete, tp_name)) |ety| break :blk ety;
}
break :blk null;
},
else => null,
};
}