lang: reject unbindable $T-only generic returns at declaration (audit follow-up)
This commit is contained in:
@@ -1966,6 +1966,41 @@ pub fn bareAuthorFuncId(self: *Lowering, fd: *const ast.FnDecl, name: []const u8
|
||||
return fid;
|
||||
}
|
||||
|
||||
/// Walk a return-type expression for a `$T` generic leaf, returning the
|
||||
/// first generic name found. The parser builds `fd.type_params` from
|
||||
/// PARAMS only (`collectTypeParams`), so a `$`-generic that appears ONLY
|
||||
/// in the return type makes the fn look non-generic while its return can
|
||||
/// never be bound — `declareFunction` rejects that shape loudly.
|
||||
fn returnGenericLeaf(node: *const Node) ?[]const u8 {
|
||||
return switch (node.data) {
|
||||
.type_expr => |te| if (te.is_generic) te.name else null,
|
||||
.pointer_type_expr => |pte| returnGenericLeaf(pte.pointee_type),
|
||||
.many_pointer_type_expr => |mpte| returnGenericLeaf(mpte.element_type),
|
||||
.slice_type_expr => |ste| returnGenericLeaf(ste.element_type),
|
||||
.array_type_expr => |ate| returnGenericLeaf(ate.element_type),
|
||||
.optional_type_expr => |ote| returnGenericLeaf(ote.inner_type),
|
||||
.parameterized_type_expr => |pte| {
|
||||
for (pte.args) |arg| if (returnGenericLeaf(arg)) |n| return n;
|
||||
return null;
|
||||
},
|
||||
.tuple_type_expr => |tte| {
|
||||
for (tte.field_types) |ft| if (returnGenericLeaf(ft)) |n| return n;
|
||||
return null;
|
||||
},
|
||||
.closure_type_expr => |cte| {
|
||||
for (cte.param_types) |pt| if (returnGenericLeaf(pt)) |n| return n;
|
||||
if (cte.return_type) |rt| return returnGenericLeaf(rt);
|
||||
return null;
|
||||
},
|
||||
.function_type_expr => |fte| {
|
||||
for (fte.param_types) |pt| if (returnGenericLeaf(pt)) |n| return n;
|
||||
if (fte.return_type) |rt| return returnGenericLeaf(rt);
|
||||
return null;
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Declare a function as an extern stub (signature only, no body).
|
||||
pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8) void {
|
||||
// Skip generic templates — they're monomorphized on demand, not declared as extern
|
||||
@@ -1973,6 +2008,23 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
|
||||
|
||||
const ret_ty = self.resolveReturnType(fd);
|
||||
|
||||
// A `$T`-generic return with NO parameter mentioning `$T`: the fn isn't
|
||||
// a template (the guard above runs on param-derived `type_params`) yet
|
||||
// its return can never be bound by any call site. Declaring it would
|
||||
// carry the `.unresolved` sentinel into LLVM emission and panic the
|
||||
// tripwire — diagnose at the declaration instead. Named unknown types
|
||||
// (`-> Bogus`) are covered by the semantic pass's "unknown type".
|
||||
if (ret_ty == .unresolved) {
|
||||
if (fd.return_type) |rtn| {
|
||||
if (returnGenericLeaf(rtn)) |gen_name| {
|
||||
if (self.diagnostics) |d| {
|
||||
d.addFmt(.err, rtn.span, "generic return type '${s}' cannot be bound — '{s}' has no parameter mentioning '${s}', so no call site can infer it", .{ gen_name, name, gen_name });
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Foreign declarations with a trailing variadic param map to the C
|
||||
// calling convention's `...` tail. Drop the variadic param from the
|
||||
// IR signature (it has no C-level slot) and set is_variadic.
|
||||
|
||||
Reference in New Issue
Block a user