lang: reject unbindable $T-only generic returns at declaration (audit follow-up)
This commit is contained in:
24
examples/1165-diagnostics-generic-return-unbound.sx
Normal file
24
examples/1165-diagnostics-generic-return-unbound.sx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// A `$T`-generic RETURN type with no parameter mentioning `$T` is rejected
|
||||||
|
// at the declaration: the fn isn't a template (type params derive from
|
||||||
|
// params), and no call site could ever bind the return. All three declare
|
||||||
|
// surfaces diagnose: a top-level fn, a struct-body method, and a
|
||||||
|
// (non-parameterised) impl method. Each used to PANIC the compiler at LLVM
|
||||||
|
// emission via the `.unresolved` tripwire — even when never called.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
make :: () -> $T { 0 }
|
||||||
|
|
||||||
|
Foo :: struct {
|
||||||
|
x: s64;
|
||||||
|
weird :: (self: *Foo) -> $T { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
Show2 :: protocol { show2 :: () -> string; }
|
||||||
|
IntBox :: struct { v: s64; }
|
||||||
|
impl Show2 for IntBox {
|
||||||
|
show2 :: (self: *IntBox) -> string { "x" }
|
||||||
|
leak :: (self: *IntBox) -> $T { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () { print("ok\n"); }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
error: generic return type '$T' cannot be bound — 'make' has no parameter mentioning '$T', so no call site can infer it
|
||||||
|
--> examples/1165-diagnostics-generic-return-unbound.sx:10:15
|
||||||
|
|
|
||||||
|
10 | make :: () -> $T { 0 }
|
||||||
|
| ^^
|
||||||
|
|
||||||
|
error: generic return type '$T' cannot be bound — 'Foo.weird' has no parameter mentioning '$T', so no call site can infer it
|
||||||
|
--> examples/1165-diagnostics-generic-return-unbound.sx:14:30
|
||||||
|
|
|
||||||
|
14 | weird :: (self: *Foo) -> $T { 0 }
|
||||||
|
| ^^
|
||||||
|
|
||||||
|
error: generic return type '$T' cannot be bound — 'IntBox.leak' has no parameter mentioning '$T', so no call site can infer it
|
||||||
|
--> examples/1165-diagnostics-generic-return-unbound.sx:21:32
|
||||||
|
|
|
||||||
|
21 | leak :: (self: *IntBox) -> $T { 0 }
|
||||||
|
| ^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1966,6 +1966,41 @@ pub fn bareAuthorFuncId(self: *Lowering, fd: *const ast.FnDecl, name: []const u8
|
|||||||
return fid;
|
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).
|
/// Declare a function as an extern stub (signature only, no body).
|
||||||
pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8) void {
|
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
|
// 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);
|
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
|
// Foreign declarations with a trailing variadic param map to the C
|
||||||
// calling convention's `...` tail. Drop the variadic param from the
|
// calling convention's `...` tail. Drop the variadic param from the
|
||||||
// IR signature (it has no C-level slot) and set is_variadic.
|
// IR signature (it has no C-level slot) and set is_variadic.
|
||||||
|
|||||||
Reference in New Issue
Block a user