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:
@@ -401,6 +401,29 @@ pub const ExprTyper = struct {
|
||||
}
|
||||
break :blk self.l.inferExprType(nc.rhs);
|
||||
},
|
||||
// A lambda literal's type is the closure it denotes, recovered from
|
||||
// its annotations. The generic-call binder types args from the raw
|
||||
// AST (notably the UFCS path, before args are lowered), so without
|
||||
// this a `Closure(..) -> $R` worker couldn't bind `$R` from the
|
||||
// lambda's declared return type (issue 0151). An unannotated param /
|
||||
// body-inferred return stays `.unresolved` here — that arg simply
|
||||
// doesn't contribute a binding, exactly as before.
|
||||
.lambda => |lam| blk: {
|
||||
var pbuf = std.ArrayList(TypeId).empty;
|
||||
defer pbuf.deinit(self.l.alloc);
|
||||
for (lam.params) |p| {
|
||||
const pty: TypeId = if (p.type_expr.data == .inferred_type)
|
||||
.unresolved
|
||||
else
|
||||
self.l.resolveTypeWithBindings(p.type_expr);
|
||||
pbuf.append(self.l.alloc, pty) catch {};
|
||||
}
|
||||
const ret: TypeId = if (lam.return_type) |rt|
|
||||
self.l.resolveTypeWithBindings(rt)
|
||||
else
|
||||
.unresolved;
|
||||
break :blk self.l.module.types.closureType(pbuf.items, ret);
|
||||
},
|
||||
// Inline asm result type (0→void, 1→T, N→named tuple) — the single
|
||||
// owner is `Lowering.asmResultType`, shared with `lowerAsmExpr` so a
|
||||
// `return asm`, a `x := asm`, and a `q, r := asm` destructure all
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user