From a61685772d929ca801adcf73071d977f212acbd9 Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 1 Jun 2026 22:18:47 +0300 Subject: [PATCH] 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. --- ...043-errors-lambda-raise-annotation-hint.sx | 20 +++++++++++++++++++ ...3-errors-lambda-raise-annotation-hint.exit | 1 + ...errors-lambda-raise-annotation-hint.stderr | 5 +++++ ...errors-lambda-raise-annotation-hint.stdout | 1 + src/ir/lower.zig | 15 ++++++++++++-- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 examples/1043-errors-lambda-raise-annotation-hint.sx create mode 100644 examples/expected/1043-errors-lambda-raise-annotation-hint.exit create mode 100644 examples/expected/1043-errors-lambda-raise-annotation-hint.stderr create mode 100644 examples/expected/1043-errors-lambda-raise-annotation-hint.stdout diff --git a/examples/1043-errors-lambda-raise-annotation-hint.sx b/examples/1043-errors-lambda-raise-annotation-hint.sx new file mode 100644 index 0000000..0ab482b --- /dev/null +++ b/examples/1043-errors-lambda-raise-annotation-hint.sx @@ -0,0 +1,20 @@ +// A closure literal whose body `raise`s but is annotated non-failable (or has +// no `!` in its return) gets a LAMBDA-SPECIFIC diagnostic telling the user to +// declare the failable return explicitly (ERR E5.1 sub-feature 1). This is the +// closure analog of the top-level "raise is only valid inside a failable +// function" error — failability is never inferred for a lambda, it must be +// declared, so a raising lambda with no `!` is a hard error pointing at the fix. + +#import "modules/std.sx"; + +E :: error { Neg } + +take :: (cb: Closure(s32) -> (s32, !E), x: s32) -> s32 { return cb(x) catch e -1; } + +main :: () -> s32 { + // `-> s32` (non-failable) but the body raises → lambda-specific hint: + // "lambda body raises; declare its return type explicitly with + // `-> (T, !)` or `-> (T, !Named)`" + print("{}\n", take(closure((x: s32) -> s32 { if x < 0 { raise error.Neg; } return x; }), -1)); + return 0; +} diff --git a/examples/expected/1043-errors-lambda-raise-annotation-hint.exit b/examples/expected/1043-errors-lambda-raise-annotation-hint.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/examples/expected/1043-errors-lambda-raise-annotation-hint.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1043-errors-lambda-raise-annotation-hint.stderr b/examples/expected/1043-errors-lambda-raise-annotation-hint.stderr new file mode 100644 index 0000000..11c936e --- /dev/null +++ b/examples/expected/1043-errors-lambda-raise-annotation-hint.stderr @@ -0,0 +1,5 @@ +error: lambda body raises; declare its return type explicitly with `-> (T, !)` or `-> (T, !Named)` + --> /Users/agra/projects/sx/examples/1043-errors-lambda-raise-annotation-hint.sx:18:61 + | +18 | print("{}\n", take(closure((x: s32) -> s32 { if x < 0 { raise error.Neg; } return x; }), -1)); + | ^^^^^^^^^^^^^^^^ diff --git a/examples/expected/1043-errors-lambda-raise-annotation-hint.stdout b/examples/expected/1043-errors-lambda-raise-annotation-hint.stdout new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/1043-errors-lambda-raise-annotation-hint.stdout @@ -0,0 +1 @@ + diff --git a/src/ir/lower.zig b/src/ir/lower.zig index d7c2418..35e6e9f 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -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`)", .{}); + } } }