ERR/E2.1a: value-carrying failable producer (return value + raise → tuple ABI)
The producer side of the error-channel tuple ABI for value-carrying `-> (T, !)`
functions. A failable that returns a value OR an error now lowers correctly;
the result is consumed via destructure (`v, err := f()`). Single-value
`-> (T, !)`; multi-value `-> (T1, T2, !)` and the value-carrying try/catch
consumers (E2.1b) follow.
- lowerReturn: a value-carrying failable's `return v;` assembles the success
tuple `{v, 0}` (compiler appends the no-error slot) via lowerFailableSuccessReturn
(tuple_init). Forwarding a full failable tuple (`return other_failable()` /
explicit `return (v, e)`) returns as-is. Multi-value returns bail loudly (E2).
- lowerRaise: the value-carrying branch (previously a loud bail) now builds
`{undef value slots..., tag}` (constUndef per value slot + the error tag) and
returns it — any arity.
- helpers: buildFailableTuple (tuple_init from value refs + tag) + emitTupleRet
(return honoring inline-comptime targets).
Value-carrying `try` / `catch` still bail (E2.1b). Tests:
examples/228-value-failable.sx (return value + both raises, consumed by
destructure; exit 60). Gates: zig build, zig build test, 266/266 examples.
This commit is contained in:
@@ -1740,6 +1740,10 @@ pub const Lowering = struct {
|
||||
if (ret_ty == .void) {
|
||||
// Void function — just return void (the value expression was evaluated for side effects)
|
||||
self.builder.retVoid();
|
||||
} else if (!ret_ty.isBuiltin() and self.module.types.get(ret_ty) == .tuple and self.errorChannelOf(ret_ty) != null) {
|
||||
// Value-carrying failable `-> (T..., !)`: the user returns the
|
||||
// value part; the compiler appends the success error slot (0).
|
||||
self.lowerFailableSuccessReturn(ref, ret_ty, rs.value.?.span);
|
||||
} else {
|
||||
// Coerce return value to match function return type (e.g., ?s32 → s32)
|
||||
const val_ty = self.builder.getRefType(ref);
|
||||
@@ -15154,11 +15158,64 @@ pub const Lowering = struct {
|
||||
self.builder.ret(coerced, err_set);
|
||||
}
|
||||
} else {
|
||||
// Value-carrying `-> (T..., !)`: needs undef value slots + the error
|
||||
// slot, assembled per the error-channel tuple ABI (ERR E2.1/E2.2).
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, span, "`raise` in a value-carrying failable function (`-> (T..., !)`) is not yet lowered — pending the error-channel tuple ABI (ERR E2); use a `-> !` / `-> !Named` signature for now", .{});
|
||||
// Value-carrying `-> (T..., !)`: the error path leaves the value
|
||||
// slots undefined and carries the tag in the error slot (ERR E2.1).
|
||||
self.emitBlockDefers(self.func_defer_base);
|
||||
const fields = self.module.types.get(ret_ty).tuple.fields;
|
||||
var slots = std.ArrayList(Ref).empty;
|
||||
defer slots.deinit(self.alloc);
|
||||
for (fields[0 .. fields.len - 1]) |vty| {
|
||||
slots.append(self.alloc, self.builder.constUndef(vty)) catch unreachable;
|
||||
}
|
||||
const tag_ty = self.builder.getRefType(tag_ref);
|
||||
const coerced_tag = if (tag_ty != err_set) self.coerceToType(tag_ref, tag_ty, err_set) else tag_ref;
|
||||
const tup = self.buildFailableTuple(ret_ty, slots.items, coerced_tag);
|
||||
self.emitTupleRet(ret_ty, tup);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a value-carrying failable function's success tuple
|
||||
/// `{value(s)..., 0}` from `ref` (the user-returned value part). Forwarding
|
||||
/// a full failable tuple (`return other_failable()` / explicit `return
|
||||
/// (v, e)`) returns it as-is. ERR E2.1a covers the single-value `-> (T, !)`
|
||||
/// shape; multi-value `-> (T1, T2, !)` is deferred.
|
||||
fn lowerFailableSuccessReturn(self: *Lowering, ref: Ref, ret_ty: TypeId, span: ast.Span) void {
|
||||
const fields = self.module.types.get(ret_ty).tuple.fields;
|
||||
const err_ty = fields[fields.len - 1];
|
||||
const val_ty = self.builder.getRefType(ref);
|
||||
if (val_ty == ret_ty) {
|
||||
// The expression already IS the full failable tuple (forwarding).
|
||||
self.emitTupleRet(ret_ty, ref);
|
||||
return;
|
||||
}
|
||||
if (fields.len == 2) {
|
||||
const cv = self.coerceToType(ref, val_ty, fields[0]);
|
||||
const tup = self.buildFailableTuple(ret_ty, &.{cv}, self.builder.constInt(0, err_ty));
|
||||
self.emitTupleRet(ret_ty, tup);
|
||||
return;
|
||||
}
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, span, "returning a value from a multi-value failable function (`-> (T1, T2, !)`) is not yet lowered — pending the multi-value error-channel ABI (ERR E2); single-value `-> (T, !)` works", .{});
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a failable return tuple `{value_refs..., tag}` typed `ret_ty`.
|
||||
fn buildFailableTuple(self: *Lowering, ret_ty: TypeId, value_refs: []const Ref, tag: Ref) Ref {
|
||||
var fields = std.ArrayList(Ref).empty;
|
||||
defer fields.deinit(self.alloc);
|
||||
fields.appendSlice(self.alloc, value_refs) catch unreachable;
|
||||
fields.append(self.alloc, tag) catch unreachable;
|
||||
return self.builder.emit(.{ .tuple_init = .{ .fields = self.alloc.dupe(Ref, fields.items) catch unreachable } }, ret_ty);
|
||||
}
|
||||
|
||||
/// Emit a return of an already-assembled tuple, honoring inline-comptime
|
||||
/// return targets (store + branch) vs a real function return.
|
||||
fn emitTupleRet(self: *Lowering, ret_ty: TypeId, tup: Ref) void {
|
||||
if (self.inline_return_target) |iri| {
|
||||
self.builder.store(iri.slot, tup);
|
||||
self.builder.br(iri.done_bb, &.{});
|
||||
} else {
|
||||
self.builder.ret(tup, ret_ty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user