ERR/E5.1: lambda-specific raise-not-failable hint

A closure literal whose body raises but is annotated non-failable (or has
no ! in its return) now gets a lambda-specific diagnostic telling the user
to declare the failable return explicitly, instead of the generic "raise
is only valid inside a failable function". Failability is never inferred
for a lambda, so a raising lambda with no ! is a hard error that should
point at the fix.

New in_lambda_body flag (save/restore for nesting) set around the lambda
body lowering in lowerLambda; diagRaiseNotFailable branches on it.
Top-level functions keep the generic message.

Test: 1043-errors-lambda-raise-annotation-hint.sx.
This commit is contained in:
agra
2026-06-01 22:18:47 +03:00
parent 34bdf8b87c
commit a61685772d
5 changed files with 40 additions and 2 deletions

View File

@@ -135,6 +135,7 @@ pub const Lowering = struct {
current_match_tags: ?[]const u64 = null, // type tags for current match arm (for runtime dispatch)
force_block_value: bool = false, // set by lowerBlockValue to extract if-else values
block_terminated: bool = false, // set when constant-folded if emits a return/br into current block
in_lambda_body: bool = false, // true while lowering a closure-literal body; sharpens the `raise`-not-failable diagnostic (ERR E5.1: tell the user to annotate `-> (T, !)`)
defer_stack: std.ArrayList(CleanupEntry) = std.ArrayList(CleanupEntry).empty, // block-scoped defer + onfail cleanup stack
func_defer_base: usize = 0, // defer stack base for current function (lowerReturn drains to this)
global_names: std.StringHashMap(GlobalInfo) = std.StringHashMap(GlobalInfo).init(std.heap.page_allocator), // #run global name → GlobalId
@@ -7867,7 +7868,12 @@ pub const Lowering = struct {
lambda_scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });
}
// Lower body — capture last expression as return value
// Lower body — capture last expression as return value. The
// `in_lambda_body` flag scopes the lambda-specific `raise`-not-failable
// hint; save/restore so a lambda nested inside a regular function (or a
// lambda inside a lambda) restores the enclosing context.
const saved_in_lambda = self.in_lambda_body;
self.in_lambda_body = true;
if (ret_ty != .void) {
if (self.lowerBlockValue(lam.body)) |val| {
if (!self.currentBlockHasTerminator()) {
@@ -7886,6 +7892,7 @@ pub const Lowering = struct {
} else {
self.lowerBlock(lam.body);
}
self.in_lambda_body = saved_in_lambda;
self.ensureTerminator(ret_ty);
self.builder.finalize();
@@ -15839,7 +15846,11 @@ pub const Lowering = struct {
fn diagRaiseNotFailable(self: *Lowering, span: ast.Span) void {
if (self.diagnostics) |diags| {
diags.addFmt(.err, span, "`raise` is only valid inside a failable function (a return type with `!` or `!Named`)", .{});
if (self.in_lambda_body) {
diags.addFmt(.err, span, "lambda body raises; declare its return type explicitly with `-> (T, !)` or `-> (T, !Named)`", .{});
} else {
diags.addFmt(.err, span, "`raise` is only valid inside a failable function (a return type with `!` or `!Named`)", .{});
}
}
}