ERR/E2: multi-value failables -> (T1, ..., !)
Generalize the single-value `-> (T, !)` error-channel ABI to any value arity. Retire the five `fields.len == 2` bails (lowerFailableSuccessReturn, lowerTry, lowerCatch, lowerFailableOr, and the inferExprType try/catch/or arms); lowerRaise + emitErrorReturn already looped over N value slots. New helpers centralize "value-part = every slot but the last (error) one": failableSuccessType (lone value type, or a value-tuple), extractSuccessValue, extractErrorSlot. Fix one latent bug the feature surfaced: coerceToType had no tuple->tuple arm, so a value-tuple flowing into a differently-typed success slot (e.g. (s64,s64) catch body into (s32,s32)) fell through unchanged. Add element-wise coercion. No lowerTupleLiteral change is needed: a `return (a, b)` literal against a 3-field failable target already gets target_fields=null via the arity mismatch, so it types as a plain value-tuple that lowerFailableSuccessReturn consumes. examples/235-multi-value-failable.sx exercises producer return/raise, destructure (binding every slot incl. the error tag), multi-value try (success + propagation), catch (bare-expr tuple body), and or-tuple terminator. Match-body tuple arms are left out: `(` after `case PAT:` is parsed as a payload capture (a pre-existing, multi-value-unrelated parser bug). Gates: zig build, zig build test, 273/273 examples.
This commit is contained in:
64
examples/235-multi-value-failable.sx
Normal file
64
examples/235-multi-value-failable.sx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// Multi-value value-carrying failables (ERR — the multi-value error-channel
|
||||||
|
// ABI). A `-> (T1, T2, !E)` function returns EITHER a value-tuple OR an error:
|
||||||
|
// `return (a, b)` yields the success tuple `{a, b, 0}` (the compiler appends the
|
||||||
|
// no-error slot) and `raise error.X` yields `{undef, undef, tag}`. Every consumer
|
||||||
|
// generalizes from the single-value shape: a destructure binds every slot
|
||||||
|
// INCLUDING the error (dropping it is the spec'd discard error — bind it and
|
||||||
|
// inspect); `try` binds the value-tuple on success and propagates `{undef..., tag}`
|
||||||
|
// on failure; `catch` / `or` absorb the failure and merge the value-tuple or the
|
||||||
|
// handler/terminator value. Single-value `-> (T, !E)` is examples/228-231.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
E :: error { Bad, Empty }
|
||||||
|
|
||||||
|
parse :: (n: s32) -> (s32, s32, !E) {
|
||||||
|
if n < 0 { raise error.Bad; }
|
||||||
|
if n == 0 { raise error.Empty; }
|
||||||
|
return (n * 2, n + 1); // success → {n*2, n+1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-value `try` in a multi-value caller — propagates {undef, undef, tag}.
|
||||||
|
inc :: (n: s32) -> (s32, s32, !E) {
|
||||||
|
v, b := try parse(n);
|
||||||
|
return (v + 1, b + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-value `catch`, bare-expression tuple fallback (absorbs the failure).
|
||||||
|
safe :: (n: s32) -> s32 {
|
||||||
|
v, b := parse(n) catch e (40, 50);
|
||||||
|
return v + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-value `or (tuple)` value-terminator (absorbs the failure).
|
||||||
|
ortest :: (n: s32) -> s32 {
|
||||||
|
v, b := parse(n) or (7, 8);
|
||||||
|
return v + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
r : s32 = 0;
|
||||||
|
|
||||||
|
// Destructure binds EVERY slot including the error tag (e1 / e2 / e3) —
|
||||||
|
// the error is treated, never dropped.
|
||||||
|
v1, b1, e1 := parse(5); // success → (10, 6, no-error)
|
||||||
|
if e1 == error.Bad { r = r + 1000; } // false
|
||||||
|
r = r + v1 + b1; // +16
|
||||||
|
|
||||||
|
v2, b2, e2 := parse(-1); // Bad → {undef, undef, Bad}
|
||||||
|
if e2 == error.Bad { r = r + 4; } // +4
|
||||||
|
|
||||||
|
a, c, ea := inc(5); // parse(5)=(10,6) → (11, 7, no-error)
|
||||||
|
if ea == error.Bad { r = r + 2000; } // false
|
||||||
|
r = r + a + c; // +18
|
||||||
|
|
||||||
|
a2, c2, e3 := inc(-1); // try parse(-1)=Bad → propagate {undef, undef, Bad}
|
||||||
|
if e3 == error.Bad { r = r + 5; } // +5
|
||||||
|
|
||||||
|
r = r + safe(5); // (10, 6) → 16
|
||||||
|
r = r + safe(-1); // Bad → catch → (40, 50) → 90
|
||||||
|
r = r + ortest(0); // Empty → or → (7, 8) → 15
|
||||||
|
|
||||||
|
print("multi-value result: {}\n", r); // 16+4+18+5+16+90+15 = 164
|
||||||
|
return r;
|
||||||
|
}
|
||||||
168
src/ir/lower.zig
168
src/ir/lower.zig
@@ -13645,8 +13645,7 @@ pub const Lowering = struct {
|
|||||||
const lt = self.inferExprType(bop.lhs);
|
const lt = self.inferExprType(bop.lhs);
|
||||||
if (self.errorChannelOf(lt)) |ch| {
|
if (self.errorChannelOf(lt)) |ch| {
|
||||||
if (lt == ch) break :blk .unresolved; // pure-failable (rejected at lowering)
|
if (lt == ch) break :blk .unresolved; // pure-failable (rejected at lowering)
|
||||||
const f = self.module.types.get(lt).tuple.fields;
|
break :blk self.failableSuccessType(lt);
|
||||||
break :blk if (f.len == 2) f[0] else .unresolved;
|
|
||||||
}
|
}
|
||||||
break :blk .bool;
|
break :blk .bool;
|
||||||
},
|
},
|
||||||
@@ -13665,15 +13664,13 @@ pub const Lowering = struct {
|
|||||||
},
|
},
|
||||||
// `try X` evaluates to X's success type (the value part). A
|
// `try X` evaluates to X's success type (the value part). A
|
||||||
// pure-failable operand (`-> !` / `-> !Named`, whose type IS the
|
// pure-failable operand (`-> !` / `-> !Named`, whose type IS the
|
||||||
// error set) has no value → `void`; a value-carrying `-> (T, !)`
|
// error set) has no value → `void`; a value-carrying `-> (T..., !)`
|
||||||
// operand yields its value part (single field, or the tuple).
|
// operand yields its value part (the lone value, or a value-tuple).
|
||||||
.try_expr => |te| blk: {
|
.try_expr => |te| blk: {
|
||||||
const op_ty = self.inferExprType(te.operand);
|
const op_ty = self.inferExprType(te.operand);
|
||||||
const channel = self.errorChannelOf(op_ty) orelse break :blk .unresolved;
|
const channel = self.errorChannelOf(op_ty) orelse break :blk .unresolved;
|
||||||
if (op_ty == channel) break :blk .void;
|
if (op_ty == channel) break :blk .void;
|
||||||
const info = self.module.types.get(op_ty);
|
break :blk self.failableSuccessType(op_ty);
|
||||||
if (info == .tuple and info.tuple.fields.len == 2) break :blk info.tuple.fields[0];
|
|
||||||
break :blk op_ty;
|
|
||||||
},
|
},
|
||||||
// `expr catch ...` strips the error channel → the success type
|
// `expr catch ...` strips the error channel → the success type
|
||||||
// (void for a pure-failable LHS; the value part for value-carrying).
|
// (void for a pure-failable LHS; the value part for value-carrying).
|
||||||
@@ -13681,9 +13678,7 @@ pub const Lowering = struct {
|
|||||||
const op_ty = self.inferExprType(ce.operand);
|
const op_ty = self.inferExprType(ce.operand);
|
||||||
const channel = self.errorChannelOf(op_ty) orelse break :blk .unresolved;
|
const channel = self.errorChannelOf(op_ty) orelse break :blk .unresolved;
|
||||||
if (op_ty == channel) break :blk .void;
|
if (op_ty == channel) break :blk .void;
|
||||||
const info = self.module.types.get(op_ty);
|
break :blk self.failableSuccessType(op_ty);
|
||||||
if (info == .tuple and info.tuple.fields.len == 2) break :blk info.tuple.fields[0];
|
|
||||||
break :blk op_ty;
|
|
||||||
},
|
},
|
||||||
.if_expr => |ie| {
|
.if_expr => |ie| {
|
||||||
// If-else types as its branches' unified type. A `noreturn`
|
// If-else types as its branches' unified type. A `noreturn`
|
||||||
@@ -14876,6 +14871,24 @@ pub const Lowering = struct {
|
|||||||
return self.builder.boxAny(val, src_ty);
|
return self.builder.boxAny(val, src_ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tuple → Tuple element-wise coercion (e.g. a `(s64, s64)` literal
|
||||||
|
// flowing into a `(s32, s32)` slot — the multi-value failable success
|
||||||
|
// tuple). Same arity, at least one differing field (src_ty == dst_ty
|
||||||
|
// already returned above): extract each slot, coerce it, rebuild.
|
||||||
|
if (!src_ty.isBuiltin() and !dst_ty.isBuiltin()) {
|
||||||
|
const si = self.module.types.get(src_ty);
|
||||||
|
const di = self.module.types.get(dst_ty);
|
||||||
|
if (si == .tuple and di == .tuple and si.tuple.fields.len == di.tuple.fields.len) {
|
||||||
|
var elems = std.ArrayList(Ref).empty;
|
||||||
|
defer elems.deinit(self.alloc);
|
||||||
|
for (si.tuple.fields, di.tuple.fields, 0..) |sf, df, i| {
|
||||||
|
const fv = self.builder.emit(.{ .tuple_get = .{ .base = val, .field_index = @intCast(i), .base_type = src_ty } }, sf);
|
||||||
|
elems.append(self.alloc, self.coerceToType(fv, sf, df)) catch unreachable;
|
||||||
|
}
|
||||||
|
return self.builder.emit(.{ .tuple_init = .{ .fields = self.alloc.dupe(Ref, elems.items) catch unreachable } }, dst_ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Optional → Concrete unwrapping (flow-sensitive narrowing coercion)
|
// Optional → Concrete unwrapping (flow-sensitive narrowing coercion)
|
||||||
if (!src_ty.isBuiltin()) {
|
if (!src_ty.isBuiltin()) {
|
||||||
const src_info = self.module.types.get(src_ty);
|
const src_info = self.module.types.get(src_ty);
|
||||||
@@ -15251,8 +15264,9 @@ pub const Lowering = struct {
|
|||||||
/// Return a value-carrying failable function's success tuple
|
/// Return a value-carrying failable function's success tuple
|
||||||
/// `{value(s)..., 0}` from `ref` (the user-returned value part). Forwarding
|
/// `{value(s)..., 0}` from `ref` (the user-returned value part). Forwarding
|
||||||
/// a full failable tuple (`return other_failable()` / explicit `return
|
/// a full failable tuple (`return other_failable()` / explicit `return
|
||||||
/// (v, e)`) returns it as-is. ERR E2.1a covers the single-value `-> (T, !)`
|
/// (v, e)`) returns it as-is. Single-value `-> (T, !)` takes `ref` as the
|
||||||
/// shape; multi-value `-> (T1, T2, !)` is deferred.
|
/// lone value; multi-value `-> (T1, ..., !)` takes `ref` as a value-tuple
|
||||||
|
/// `(T1, ...)` and re-assembles its slots alongside the success error slot.
|
||||||
fn lowerFailableSuccessReturn(self: *Lowering, ref: Ref, ret_ty: TypeId, span: ast.Span) void {
|
fn lowerFailableSuccessReturn(self: *Lowering, ref: Ref, ret_ty: TypeId, span: ast.Span) void {
|
||||||
const fields = self.module.types.get(ret_ty).tuple.fields;
|
const fields = self.module.types.get(ret_ty).tuple.fields;
|
||||||
const err_ty = fields[fields.len - 1];
|
const err_ty = fields[fields.len - 1];
|
||||||
@@ -15262,15 +15276,31 @@ pub const Lowering = struct {
|
|||||||
self.emitTupleRet(ret_ty, ref);
|
self.emitTupleRet(ret_ty, ref);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fields.len == 2) {
|
const n_vals = fields.len - 1;
|
||||||
|
if (n_vals == 1) {
|
||||||
const cv = self.coerceToType(ref, val_ty, fields[0]);
|
const cv = self.coerceToType(ref, val_ty, fields[0]);
|
||||||
const tup = self.buildFailableTuple(ret_ty, &.{cv}, self.builder.constInt(0, err_ty));
|
const tup = self.buildFailableTuple(ret_ty, &.{cv}, self.builder.constInt(0, err_ty));
|
||||||
self.emitTupleRet(ret_ty, tup);
|
self.emitTupleRet(ret_ty, tup);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (self.diagnostics) |diags| {
|
// Multi-value: `ref` must be a value-tuple `(T1, ..., Tn)`. Extract
|
||||||
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", .{});
|
// each value slot, coerce to the declared field type, and re-assemble
|
||||||
|
// with the success error slot (0).
|
||||||
|
if (val_ty.isBuiltin() or self.module.types.get(val_ty) != .tuple or self.module.types.get(val_ty).tuple.fields.len != n_vals) {
|
||||||
|
if (self.diagnostics) |diags| {
|
||||||
|
diags.addFmt(.err, span, "a multi-value failable function (`-> (T1, ..., !)`) must `return` a {d}-tuple of its value types", .{n_vals});
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const vfields = self.module.types.get(val_ty).tuple.fields;
|
||||||
|
var vals = std.ArrayList(Ref).empty;
|
||||||
|
defer vals.deinit(self.alloc);
|
||||||
|
for (0..n_vals) |i| {
|
||||||
|
const fv = self.builder.emit(.{ .tuple_get = .{ .base = ref, .field_index = @intCast(i), .base_type = val_ty } }, vfields[i]);
|
||||||
|
vals.append(self.alloc, self.coerceToType(fv, vfields[i], fields[i])) catch unreachable;
|
||||||
|
}
|
||||||
|
const tup = self.buildFailableTuple(ret_ty, vals.items, self.builder.constInt(0, err_ty));
|
||||||
|
self.emitTupleRet(ret_ty, tup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a failable return tuple `{value_refs..., tag}` typed `ret_ty`.
|
/// Build a failable return tuple `{value_refs..., tag}` typed `ret_ty`.
|
||||||
@@ -15282,6 +15312,45 @@ pub const Lowering = struct {
|
|||||||
return self.builder.emit(.{ .tuple_init = .{ .fields = self.alloc.dupe(Ref, fields.items) catch unreachable } }, ret_ty);
|
return self.builder.emit(.{ .tuple_init = .{ .fields = self.alloc.dupe(Ref, fields.items) catch unreachable } }, ret_ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The success (value-part) type of a value-carrying failable tuple
|
||||||
|
/// `op_ty` (`-> (T..., !)`): the lone value type for a single-value
|
||||||
|
/// failable, or a synthesized value-tuple `(T1, ..., Tn)` (error slot
|
||||||
|
/// dropped) for a multi-value one. Callers must pass a value-carrying
|
||||||
|
/// tuple — a pure `-> !`'s success type is `void`, handled separately.
|
||||||
|
fn failableSuccessType(self: *Lowering, op_ty: TypeId) TypeId {
|
||||||
|
const fields = self.module.types.get(op_ty).tuple.fields;
|
||||||
|
const n_vals = fields.len - 1;
|
||||||
|
if (n_vals == 1) return fields[0];
|
||||||
|
return self.module.types.intern(.{ .tuple = .{
|
||||||
|
.fields = self.alloc.dupe(TypeId, fields[0..n_vals]) catch unreachable,
|
||||||
|
.names = null,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the success value from an evaluated value-carrying failable
|
||||||
|
/// tuple `result` (type `op_ty`): the lone value slot for single-value,
|
||||||
|
/// or an assembled value-tuple (typed `succ_ty`) for multi-value.
|
||||||
|
fn extractSuccessValue(self: *Lowering, result: Ref, op_ty: TypeId, succ_ty: TypeId) Ref {
|
||||||
|
const fields = self.module.types.get(op_ty).tuple.fields;
|
||||||
|
const n_vals = fields.len - 1;
|
||||||
|
if (n_vals == 1) {
|
||||||
|
return self.builder.emit(.{ .tuple_get = .{ .base = result, .field_index = 0, .base_type = op_ty } }, fields[0]);
|
||||||
|
}
|
||||||
|
var vals = std.ArrayList(Ref).empty;
|
||||||
|
defer vals.deinit(self.alloc);
|
||||||
|
for (0..n_vals) |i| {
|
||||||
|
vals.append(self.alloc, self.builder.emit(.{ .tuple_get = .{ .base = result, .field_index = @intCast(i), .base_type = op_ty } }, fields[i])) catch unreachable;
|
||||||
|
}
|
||||||
|
return self.builder.emit(.{ .tuple_init = .{ .fields = self.alloc.dupe(Ref, vals.items) catch unreachable } }, succ_ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the error slot (always the last field) of an evaluated
|
||||||
|
/// value-carrying failable tuple `result`, typed as `err_set`.
|
||||||
|
fn extractErrorSlot(self: *Lowering, result: Ref, op_ty: TypeId, err_set: TypeId) Ref {
|
||||||
|
const fields = self.module.types.get(op_ty).tuple.fields;
|
||||||
|
return self.builder.emit(.{ .tuple_get = .{ .base = result, .field_index = @intCast(fields.len - 1), .base_type = op_ty } }, err_set);
|
||||||
|
}
|
||||||
|
|
||||||
/// Emit a return of an already-assembled tuple, honoring inline-comptime
|
/// Emit a return of an already-assembled tuple, honoring inline-comptime
|
||||||
/// return targets (store + branch) vs a real function return.
|
/// return targets (store + branch) vs a real function return.
|
||||||
fn emitTupleRet(self: *Lowering, ret_ty: TypeId, tup: Ref) void {
|
fn emitTupleRet(self: *Lowering, ret_ty: TypeId, tup: Ref) void {
|
||||||
@@ -15337,13 +15406,10 @@ pub const Lowering = struct {
|
|||||||
return self.builder.constInt(0, .void);
|
return self.builder.constInt(0, .void);
|
||||||
};
|
};
|
||||||
|
|
||||||
// A value-carrying callee (`-> (T, !)`) returns a tuple `{v, err}`; a
|
// A value-carrying callee (`-> (T..., !)`) returns a tuple
|
||||||
// pure-failable callee (`-> !`) returns the bare error tag. Multi-value
|
// `{v..., err}`; a pure-failable callee (`-> !`) returns the bare
|
||||||
// callees (`-> (T1, T2, !)`) need multi-slot extraction — deferred.
|
// error tag.
|
||||||
const callee_value_carrying = op_ty != callee_set;
|
const callee_value_carrying = op_ty != callee_set;
|
||||||
if (callee_value_carrying and self.module.types.get(op_ty).tuple.fields.len != 2) {
|
|
||||||
return self.bailTry(span, "a multi-value failable callee (`-> (T1, T2, !)`)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// (3) Widening: the callee's escape set must be ⊆ the caller's named
|
// (3) Widening: the callee's escape set must be ⊆ the caller's named
|
||||||
// set. For an inferred caller (`!`) the absorption happens in the
|
// set. For an inferred caller (`!`) the absorption happens in the
|
||||||
@@ -15368,7 +15434,7 @@ pub const Lowering = struct {
|
|||||||
// a value-carrying one).
|
// a value-carrying one).
|
||||||
const result = self.lowerExpr(operand);
|
const result = self.lowerExpr(operand);
|
||||||
const err_val = if (callee_value_carrying)
|
const err_val = if (callee_value_carrying)
|
||||||
self.builder.emit(.{ .tuple_get = .{ .base = result, .field_index = 1, .base_type = op_ty } }, callee_set)
|
self.extractErrorSlot(result, op_ty, callee_set)
|
||||||
else
|
else
|
||||||
result;
|
result;
|
||||||
const err_ty = self.builder.getRefType(err_val);
|
const err_ty = self.builder.getRefType(err_val);
|
||||||
@@ -15385,12 +15451,12 @@ pub const Lowering = struct {
|
|||||||
self.emitErrorCleanup(self.func_defer_base, err_val);
|
self.emitErrorCleanup(self.func_defer_base, err_val);
|
||||||
self.emitErrorReturn(caller_ret, caller_set, err_val);
|
self.emitErrorReturn(caller_ret, caller_set, err_val);
|
||||||
|
|
||||||
// Success: a value-carrying callee yields its value slot; a
|
// Success: a value-carrying callee yields its value part (the lone
|
||||||
// pure-failable callee has no value (void).
|
// value, or a value-tuple); a pure-failable callee has no value (void).
|
||||||
self.builder.switchToBlock(ok_bb);
|
self.builder.switchToBlock(ok_bb);
|
||||||
if (callee_value_carrying) {
|
if (callee_value_carrying) {
|
||||||
const succ_ty = self.module.types.get(op_ty).tuple.fields[0];
|
const succ_ty = self.failableSuccessType(op_ty);
|
||||||
return self.builder.emit(.{ .tuple_get = .{ .base = result, .field_index = 0, .base_type = op_ty } }, succ_ty);
|
return self.extractSuccessValue(result, op_ty, succ_ty);
|
||||||
}
|
}
|
||||||
return self.builder.constInt(0, .void);
|
return self.builder.constInt(0, .void);
|
||||||
}
|
}
|
||||||
@@ -15460,20 +15526,14 @@ pub const Lowering = struct {
|
|||||||
return self.builder.constInt(0, .void);
|
return self.builder.constInt(0, .void);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value-carrying LHS (`-> (T, !)`): on success the catch yields the
|
// Value-carrying LHS (`-> (T..., !)`): on success the catch yields the
|
||||||
// value slot; on error it yields the handler body's value. The paths
|
// value part (the lone value, or a value-tuple); on error it yields
|
||||||
// merge through a block-parameter (phi). Multi-value is deferred.
|
// the handler body's value. The paths merge through a block-parameter
|
||||||
const fields = self.module.types.get(op_ty).tuple.fields;
|
// (phi).
|
||||||
if (fields.len != 2) {
|
const succ_ty = self.failableSuccessType(op_ty);
|
||||||
if (self.diagnostics) |diags| {
|
|
||||||
diags.addFmt(.err, span, "`catch` on a multi-value failable (`-> (T1, T2, !)`) is not yet lowered — pending the multi-value error-channel ABI (ERR E2)", .{});
|
|
||||||
}
|
|
||||||
return self.builder.constInt(0, .void);
|
|
||||||
}
|
|
||||||
const succ_ty = fields[0];
|
|
||||||
const result = self.lowerExpr(ce.operand);
|
const result = self.lowerExpr(ce.operand);
|
||||||
const err_val = self.builder.emit(.{ .tuple_get = .{ .base = result, .field_index = 1, .base_type = op_ty } }, err_set);
|
const err_val = self.extractErrorSlot(result, op_ty, err_set);
|
||||||
const succ_val = self.builder.emit(.{ .tuple_get = .{ .base = result, .field_index = 0, .base_type = op_ty } }, succ_ty);
|
const succ_val = self.extractSuccessValue(result, op_ty, succ_ty);
|
||||||
const is_err = self.builder.emit(.{ .cmp_ne = .{ .lhs = err_val, .rhs = self.builder.constInt(0, err_set) } }, .bool);
|
const is_err = self.builder.emit(.{ .cmp_ne = .{ .lhs = err_val, .rhs = self.builder.constInt(0, err_set) } }, .bool);
|
||||||
|
|
||||||
const handle_bb = self.freshBlock("catch.handle");
|
const handle_bb = self.freshBlock("catch.handle");
|
||||||
@@ -15530,19 +15590,12 @@ pub const Lowering = struct {
|
|||||||
return if (ce.body.data == .block) self.lowerBlockValue(ce.body) else self.lowerExpr(ce.body);
|
return if (ce.body.data == .block) self.lowerBlockValue(ce.body) else self.lowerExpr(ce.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bailTry(self: *Lowering, span: ast.Span, comptime what: []const u8) Ref {
|
|
||||||
if (self.diagnostics) |diags| {
|
|
||||||
diags.addFmt(.err, span, "`try` with " ++ what ++ " is not yet lowered — pending the error-channel tuple ABI (ERR E2)", .{});
|
|
||||||
}
|
|
||||||
return self.builder.constInt(0, .void);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `lhs or rhs` with a failable LHS (ERR step E2.4a — the value-terminator
|
/// `lhs or rhs` with a failable LHS (ERR step E2.4a — the value-terminator
|
||||||
/// form). On LHS success the result is its value; on failure the LHS error
|
/// form). On LHS success the result is its value part (the lone value, or a
|
||||||
/// is discarded and the result is `rhs` (a plain value of the success
|
/// value-tuple); on failure the LHS error is discarded and the result is
|
||||||
/// type), so the whole expression is non-failable. Single-value
|
/// `rhs` (a plain value of the success type), so the whole expression is
|
||||||
/// value-carrying LHS only. The CHAIN form (`... or try ...` / a failable
|
/// non-failable. The CHAIN form (`... or try ...` / a failable RHS) needs
|
||||||
/// RHS) needs the fallback-target routing deferred from E1.4 — bail.
|
/// the fallback-target routing deferred from E1.4 — bail.
|
||||||
fn lowerFailableOr(self: *Lowering, bop: *const ast.BinaryOp) Ref {
|
fn lowerFailableOr(self: *Lowering, bop: *const ast.BinaryOp) Ref {
|
||||||
const span = bop.lhs.span;
|
const span = bop.lhs.span;
|
||||||
|
|
||||||
@@ -15568,18 +15621,11 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
return self.builder.constInt(0, .void);
|
return self.builder.constInt(0, .void);
|
||||||
}
|
}
|
||||||
const fields = self.module.types.get(lhs_ty).tuple.fields;
|
const succ_ty = self.failableSuccessType(lhs_ty);
|
||||||
if (fields.len != 2) {
|
|
||||||
if (self.diagnostics) |diags| {
|
|
||||||
diags.addFmt(.err, span, "`or value` on a multi-value failable (`-> (T1, T2, !)`) is not yet lowered — pending the multi-value error-channel ABI (ERR E2)", .{});
|
|
||||||
}
|
|
||||||
return self.builder.constInt(0, .void);
|
|
||||||
}
|
|
||||||
const succ_ty = fields[0];
|
|
||||||
|
|
||||||
const result = self.lowerExpr(bop.lhs);
|
const result = self.lowerExpr(bop.lhs);
|
||||||
const err_val = self.builder.emit(.{ .tuple_get = .{ .base = result, .field_index = 1, .base_type = lhs_ty } }, err_set);
|
const err_val = self.extractErrorSlot(result, lhs_ty, err_set);
|
||||||
const succ_val = self.builder.emit(.{ .tuple_get = .{ .base = result, .field_index = 0, .base_type = lhs_ty } }, succ_ty);
|
const succ_val = self.extractSuccessValue(result, lhs_ty, succ_ty);
|
||||||
const is_err = self.builder.emit(.{ .cmp_ne = .{ .lhs = err_val, .rhs = self.builder.constInt(0, err_set) } }, .bool);
|
const is_err = self.builder.emit(.{ .cmp_ne = .{ .lhs = err_val, .rhs = self.builder.constInt(0, err_set) } }, .bool);
|
||||||
|
|
||||||
const fail_bb = self.freshBlock("or.fail");
|
const fail_bb = self.freshBlock("or.fail");
|
||||||
|
|||||||
1
tests/expected/235-multi-value-failable.exit
Normal file
1
tests/expected/235-multi-value-failable.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
164
|
||||||
1
tests/expected/235-multi-value-failable.txt
Normal file
1
tests/expected/235-multi-value-failable.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
multi-value result: 164
|
||||||
Reference in New Issue
Block a user