stage2: Do not pop error trace if result is an error

This allows for errors to be "re-thrown" by yielding any error as the
result of a catch block. For example:

```zig
fn errorable() !void {
    return error.FallingOutOfPlane;
}

fn foo(have_parachute: bool) !void {
    return errorable() catch |err| b: {
        if (have_parachute) {
            // error trace will include the call to errorable()
            break :b error.NoParachute;
        } else {
            return;
        }
    };
}

pub fn main() !void {
    // Anything that returns a non-error does not pollute the error trace.
    try foo(true);

    // This error trace will still include errorable(), whose error was "re-thrown" by foo()
    try foo(false);
}
```

This is piece (2/3) of https://github.com/ziglang/zig/issues/1923#issuecomment-1218495574
This commit is contained in:
Cody Tapscott 2022-09-12 23:09:14 -07:00
parent eda3eb1561
commit 0c3a50fe1c
2 changed files with 200 additions and 89 deletions

View file

@ -223,6 +223,10 @@ pub const ResultLoc = union(enum) {
/// The expression must generate a pointer rather than a value. For example, the left hand side /// The expression must generate a pointer rather than a value. For example, the left hand side
/// of an assignment uses this kind of result location. /// of an assignment uses this kind of result location.
ref, ref,
/// Exactly like `none`, except also indicates this is an error-handling expr (try/catch/return etc.)
catch_none,
/// Exactly like `ref`, except also indicates this is an error-handling expr (try/catch/return etc.)
catch_ref,
/// The expression will be coerced into this type, but it will be evaluated as an rvalue. /// The expression will be coerced into this type, but it will be evaluated as an rvalue.
ty: Zir.Inst.Ref, ty: Zir.Inst.Ref,
/// Same as `ty` but for shift operands. /// Same as `ty` but for shift operands.
@ -265,7 +269,7 @@ pub const ResultLoc = union(enum) {
fn strategy(rl: ResultLoc, block_scope: *GenZir) Strategy { fn strategy(rl: ResultLoc, block_scope: *GenZir) Strategy {
switch (rl) { switch (rl) {
// In this branch there will not be any store_to_block_ptr instructions. // In this branch there will not be any store_to_block_ptr instructions.
.none, .ty, .ty_shift_operand, .coerced_ty, .ref => return .{ .none, .catch_none, .ty, .ty_shift_operand, .coerced_ty, .ref, .catch_ref => return .{
.tag = .break_operand, .tag = .break_operand,
.elide_store_to_block_ptr_instructions = false, .elide_store_to_block_ptr_instructions = false,
}, },
@ -838,7 +842,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
const lhs = try expr(gz, scope, .none, node_datas[node].lhs); const lhs = try expr(gz, scope, .none, node_datas[node].lhs);
_ = try gz.addUnNode(.validate_deref, lhs, node); _ = try gz.addUnNode(.validate_deref, lhs, node);
switch (rl) { switch (rl) {
.ref => return lhs, .ref, .catch_ref => return lhs,
else => { else => {
const result = try gz.addUnNode(.load, lhs, node); const result = try gz.addUnNode(.load, lhs, node);
return rvalue(gz, rl, result, node); return rvalue(gz, rl, result, node);
@ -855,7 +859,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, result, node); return rvalue(gz, rl, result, node);
}, },
.unwrap_optional => switch (rl) { .unwrap_optional => switch (rl) {
.ref => return gz.addUnNode( .ref, .catch_ref => return gz.addUnNode(
.optional_payload_safe_ptr, .optional_payload_safe_ptr,
try expr(gz, scope, .ref, node_datas[node].lhs), try expr(gz, scope, .ref, node_datas[node].lhs),
node, node,
@ -900,7 +904,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
else else
null; null;
switch (rl) { switch (rl) {
.ref => return orelseCatchExpr( .ref, .catch_ref => return orelseCatchExpr(
gz, gz,
scope, scope,
rl, rl,
@ -927,7 +931,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
} }
}, },
.@"orelse" => switch (rl) { .@"orelse" => switch (rl) {
.ref => return orelseCatchExpr( .ref, .catch_ref => return orelseCatchExpr(
gz, gz,
scope, scope,
rl, rl,
@ -1372,11 +1376,11 @@ fn arrayInitExpr(
} }
return Zir.Inst.Ref.void_value; return Zir.Inst.Ref.void_value;
}, },
.ref => { .ref, .catch_ref => {
const tag: Zir.Inst.Tag = if (types.array != .none) .array_init_ref else .array_init_anon_ref; const tag: Zir.Inst.Tag = if (types.array != .none) .array_init_ref else .array_init_anon_ref;
return arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag); return arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag);
}, },
.none => { .none, .catch_none => {
const tag: Zir.Inst.Tag = if (types.array != .none) .array_init else .array_init_anon; const tag: Zir.Inst.Tag = if (types.array != .none) .array_init else .array_init_anon;
return arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag); return arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag);
}, },
@ -1608,7 +1612,7 @@ fn structInitExpr(
} }
return Zir.Inst.Ref.void_value; return Zir.Inst.Ref.void_value;
}, },
.ref => { .ref, .catch_ref => {
if (struct_init.ast.type_expr != 0) { if (struct_init.ast.type_expr != 0) {
const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr);
_ = try gz.addUnNode(.validate_struct_init_ty, ty_inst, node); _ = try gz.addUnNode(.validate_struct_init_ty, ty_inst, node);
@ -1617,7 +1621,7 @@ fn structInitExpr(
return structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon_ref); return structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon_ref);
} }
}, },
.none => { .none, .catch_none => {
if (struct_init.ast.type_expr != 0) { if (struct_init.ast.type_expr != 0) {
const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr);
_ = try gz.addUnNode(.validate_struct_init_ty, ty_inst, node); _ = try gz.addUnNode(.validate_struct_init_ty, ty_inst, node);
@ -1891,15 +1895,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
// As our last action before the break, "pop" the error trace if needed // As our last action before the break, "pop" the error trace if needed
if (err_trace_index_to_restore != .none) { if (err_trace_index_to_restore != .none) {
// TODO: error-liveness and is_non_err // void is a non-error so we always pop - no need to call `popErrorReturnTrace`
_ = try parent_gz.addUnNode(.restore_err_ret_index, err_trace_index_to_restore, node);
_ = try parent_gz.add(.{
.tag = .restore_err_ret_index,
.data = .{ .un_node = .{
.operand = err_trace_index_to_restore,
.src_node = parent_gz.nodeIndexToRelative(node),
} },
});
} }
_ = try parent_gz.addBreak(break_tag, block_inst, .void_value); _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
@ -1914,15 +1911,15 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
// As our last action before the break, "pop" the error trace if needed // As our last action before the break, "pop" the error trace if needed
if (err_trace_index_to_restore != .none) { if (err_trace_index_to_restore != .none) {
// TODO: error-liveness and is_non_err // Pop the error trace, unless the operand is an error and breaking to an error-handling expr.
try popErrorReturnTrace(
_ = try parent_gz.add(.{ parent_gz,
.tag = .restore_err_ret_index, scope,
.data = .{ .un_node = .{ block_gz.break_result_loc,
.operand = err_trace_index_to_restore, rhs,
.src_node = parent_gz.nodeIndexToRelative(node), operand,
} }, err_trace_index_to_restore,
}); );
} }
switch (block_gz.break_result_loc) { switch (block_gz.break_result_loc) {
@ -2177,7 +2174,7 @@ fn labeledBlockExpr(
try block_scope.setBlockBody(block_inst); try block_scope.setBlockBody(block_inst);
const block_ref = indexToRef(block_inst); const block_ref = indexToRef(block_inst);
switch (rl) { switch (rl) {
.ref => return block_ref, .ref, .catch_ref => return block_ref,
else => return rvalue(gz, rl, block_ref, block_node), else => return rvalue(gz, rl, block_ref, block_node),
} }
}, },
@ -5141,14 +5138,14 @@ fn tryExpr(
const try_column = astgen.source_column; const try_column = astgen.source_column;
const operand_rl: ResultLoc = switch (rl) { const operand_rl: ResultLoc = switch (rl) {
.ref => .ref, .ref, .catch_ref => .catch_ref,
else => .none, else => .catch_none,
}; };
// This could be a pointer or value depending on the `rl` parameter. // This could be a pointer or value depending on the `rl` parameter.
const operand = try reachableExpr(parent_gz, scope, operand_rl, operand_node, node); const operand = try reachableExpr(parent_gz, scope, operand_rl, operand_node, node);
const is_inline = parent_gz.force_comptime; const is_inline = parent_gz.force_comptime;
const is_inline_bit = @as(u2, @boolToInt(is_inline)); const is_inline_bit = @as(u2, @boolToInt(is_inline));
const is_ptr_bit = @as(u2, @boolToInt(operand_rl == .ref)) << 1; const is_ptr_bit = @as(u2, @boolToInt(operand_rl == .ref or operand_rl == .catch_ref)) << 1;
const block_tag: Zir.Inst.Tag = switch (is_inline_bit | is_ptr_bit) { const block_tag: Zir.Inst.Tag = switch (is_inline_bit | is_ptr_bit) {
0b00 => .@"try", 0b00 => .@"try",
0b01 => .@"try", 0b01 => .@"try",
@ -5164,7 +5161,7 @@ fn tryExpr(
defer else_scope.unstack(); defer else_scope.unstack();
const err_tag = switch (rl) { const err_tag = switch (rl) {
.ref => Zir.Inst.Tag.err_union_code_ptr, .ref, .catch_ref => Zir.Inst.Tag.err_union_code_ptr,
else => Zir.Inst.Tag.err_union_code, else => Zir.Inst.Tag.err_union_code,
}; };
const err_code = try else_scope.addUnNode(err_tag, operand, node); const err_code = try else_scope.addUnNode(err_tag, operand, node);
@ -5175,11 +5172,86 @@ fn tryExpr(
try else_scope.setTryBody(try_inst, operand); try else_scope.setTryBody(try_inst, operand);
const result = indexToRef(try_inst); const result = indexToRef(try_inst);
switch (rl) { switch (rl) {
.ref => return result, .ref, .catch_ref => return result,
else => return rvalue(parent_gz, rl, result, node), else => return rvalue(parent_gz, rl, result, node),
} }
} }
/// Pops the error return trace, unless:
/// 1. the result is a non-error, AND
/// 2. the result location corresponds to an error-handling expression
///
/// For reference, the full list of error-handling expressions is:
/// - try X
/// - X catch ...
/// - if (X) |_| { ... } |_| { ... }
/// - return X
///
fn popErrorReturnTrace(
gz: *GenZir,
scope: *Scope,
rl: ResultLoc,
node: Ast.Node.Index,
result_inst: Zir.Inst.Ref,
error_trace_index: Zir.Inst.Ref,
) InnerError!void {
const astgen = gz.astgen;
const tree = astgen.tree;
const result_is_err = nodeMayEvalToError(tree, node);
// If we are breaking to a try/catch/error-union-if/return, the error trace propagates.
const propagate_error_trace = switch (rl) {
.catch_none, .catch_ref => true, // Propagate to try/catch/error-union-if
.ptr, .ty => |ref| b: { // Otherwise, propagate if result loc is a return
const inst = refToIndex(ref) orelse break :b false;
const zir_tags = astgen.instructions.items(.tag);
break :b zir_tags[inst] == .ret_ptr or zir_tags[inst] == .ret_type;
},
else => false,
};
if (result_is_err == .never or !propagate_error_trace) {
// We are returning a non-error, or returning to a non-error-handling operator.
// In either case, we need to pop the error trace.
_ = try gz.addUnNode(.restore_err_ret_index, error_trace_index, node);
} else if (result_is_err == .maybe) {
// We are returning to an error-handling operator with a maybe-error.
// Restore only if it's a non-error, implying the catch was successfully handled.
var block_scope = gz.makeSubBlock(scope);
block_scope.setBreakResultLoc(.discard);
defer block_scope.unstack();
// Emit conditional branch for restoring error trace index
const is_non_err = switch (rl) {
.catch_ref => try block_scope.addUnNode(.is_non_err_ptr, result_inst, node),
.ptr => |ptr| try block_scope.addUnNode(.is_non_err_ptr, ptr, node),
.ty, .catch_none => try block_scope.addUnNode(.is_non_err, result_inst, node),
else => unreachable, // Error-handling operators only generate the above result locations
};
const condbr = try block_scope.addCondBr(.condbr, node);
const block = try gz.makeBlockInst(.block, node);
try block_scope.setBlockBody(block);
// block_scope unstacked now, can add new instructions to gz
try gz.instructions.append(astgen.gpa, block);
var then_scope = block_scope.makeSubBlock(scope);
defer then_scope.unstack();
_ = try then_scope.addUnNode(.restore_err_ret_index, error_trace_index, node);
const then_break = try then_scope.makeBreak(.@"break", block, .void_value);
var else_scope = block_scope.makeSubBlock(scope);
defer else_scope.unstack();
const else_break = try else_scope.makeBreak(.@"break", block, .void_value);
try setCondBrPayload(condbr, is_non_err, &then_scope, then_break, &else_scope, else_break);
}
}
fn orelseCatchExpr( fn orelseCatchExpr(
parent_gz: *GenZir, parent_gz: *GenZir,
scope: *Scope, scope: *Scope,
@ -5204,8 +5276,8 @@ fn orelseCatchExpr(
const saved_err_trace_index = if (do_err_trace) try parent_gz.addNode(.save_err_ret_index, node) else .none; const saved_err_trace_index = if (do_err_trace) try parent_gz.addNode(.save_err_ret_index, node) else .none;
const operand_rl: ResultLoc = switch (block_scope.break_result_loc) { const operand_rl: ResultLoc = switch (block_scope.break_result_loc) {
.ref => .ref, .ref, .catch_ref => if (do_err_trace) ResultLoc{ .catch_ref = {} } else .ref,
else => .none, else => if (do_err_trace) ResultLoc{ .catch_none = {} } else .none,
}; };
block_scope.break_count += 1; block_scope.break_count += 1;
// This could be a pointer or value depending on the `operand_rl` parameter. // This could be a pointer or value depending on the `operand_rl` parameter.
@ -5227,7 +5299,7 @@ fn orelseCatchExpr(
// This could be a pointer or value depending on `unwrap_op`. // This could be a pointer or value depending on `unwrap_op`.
const unwrapped_payload = try then_scope.addUnNode(unwrap_op, operand, node); const unwrapped_payload = try then_scope.addUnNode(unwrap_op, operand, node);
const then_result = switch (rl) { const then_result = switch (rl) {
.ref => unwrapped_payload, .ref, .catch_ref => unwrapped_payload,
else => try rvalue(&then_scope, block_scope.break_result_loc, unwrapped_payload, node), else => try rvalue(&then_scope, block_scope.break_result_loc, unwrapped_payload, node),
}; };
@ -5266,15 +5338,15 @@ fn orelseCatchExpr(
if (!else_scope.endsWithNoReturn()) { if (!else_scope.endsWithNoReturn()) {
block_scope.break_count += 1; block_scope.break_count += 1;
// TODO: Add is_non_err and break check
if (do_err_trace) { if (do_err_trace) {
_ = try else_scope.add(.{ try popErrorReturnTrace(
.tag = .restore_err_ret_index, &else_scope,
.data = .{ .un_node = .{ else_sub_scope,
.operand = saved_err_trace_index, block_scope.break_result_loc,
.src_node = parent_gz.nodeIndexToRelative(node), rhs,
} }, else_result,
}); saved_err_trace_index,
);
} }
} }
try checkUsed(parent_gz, &else_scope.base, else_sub_scope); try checkUsed(parent_gz, &else_scope.base, else_sub_scope);
@ -5351,7 +5423,7 @@ fn finishThenElseBlock(
} }
const block_ref = indexToRef(main_block); const block_ref = indexToRef(main_block);
switch (rl) { switch (rl) {
.ref => return block_ref, .ref, .catch_ref => return block_ref,
else => return rvalue(parent_gz, rl, block_ref, node), else => return rvalue(parent_gz, rl, block_ref, node),
} }
}, },
@ -5375,7 +5447,7 @@ fn fieldAccess(
node: Ast.Node.Index, node: Ast.Node.Index,
) InnerError!Zir.Inst.Ref { ) InnerError!Zir.Inst.Ref {
switch (rl) { switch (rl) {
.ref => return addFieldAccess(.field_ptr, gz, scope, .ref, node), .ref, .catch_ref => return addFieldAccess(.field_ptr, gz, scope, .ref, node),
else => { else => {
const access = try addFieldAccess(.field_val, gz, scope, .none, node); const access = try addFieldAccess(.field_val, gz, scope, .none, node);
return rvalue(gz, rl, access, node); return rvalue(gz, rl, access, node);
@ -5416,7 +5488,7 @@ fn arrayAccess(
const tree = astgen.tree; const tree = astgen.tree;
const node_datas = tree.nodes.items(.data); const node_datas = tree.nodes.items(.data);
switch (rl) { switch (rl) {
.ref => return gz.addPlNode(.elem_ptr_node, node, Zir.Inst.Bin{ .ref, .catch_ref => return gz.addPlNode(.elem_ptr_node, node, Zir.Inst.Bin{
.lhs = try expr(gz, scope, .ref, node_datas[node].lhs), .lhs = try expr(gz, scope, .ref, node_datas[node].lhs),
.rhs = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs), .rhs = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs),
}), }),
@ -5514,7 +5586,7 @@ fn ifExpr(
bool_bit: Zir.Inst.Ref, bool_bit: Zir.Inst.Ref,
} = c: { } = c: {
if (if_full.error_token) |_| { if (if_full.error_token) |_| {
const cond_rl: ResultLoc = if (payload_is_ref) .ref else .none; const cond_rl: ResultLoc = if (payload_is_ref) .catch_ref else .catch_none;
const err_union = try expr(&block_scope, &block_scope.base, cond_rl, if_full.ast.cond_expr); const err_union = try expr(&block_scope, &block_scope.base, cond_rl, if_full.ast.cond_expr);
const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_err_ptr else .is_non_err; const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_err_ptr else .is_non_err;
break :c .{ break :c .{
@ -5660,6 +5732,17 @@ fn ifExpr(
const e = try expr(&else_scope, sub_scope, block_scope.break_result_loc, else_node); const e = try expr(&else_scope, sub_scope, block_scope.break_result_loc, else_node);
if (!else_scope.endsWithNoReturn()) { if (!else_scope.endsWithNoReturn()) {
block_scope.break_count += 1; block_scope.break_count += 1;
if (do_err_trace) {
try popErrorReturnTrace(
&else_scope,
sub_scope,
block_scope.break_result_loc,
else_node,
e,
saved_err_trace_index,
);
}
} }
try checkUsed(parent_gz, &else_scope.base, sub_scope); try checkUsed(parent_gz, &else_scope.base, sub_scope);
try else_scope.addDbgBlockEnd(); try else_scope.addDbgBlockEnd();
@ -5676,18 +5759,6 @@ fn ifExpr(
}, },
}; };
if (do_err_trace and !else_scope.endsWithNoReturn()) {
// TODO: is_non_err and other checks
_ = try else_scope.add(.{
.tag = .restore_err_ret_index,
.data = .{ .un_node = .{
.operand = saved_err_trace_index,
.src_node = parent_gz.nodeIndexToRelative(node),
} },
});
}
const break_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .break_inline else .@"break"; const break_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .break_inline else .@"break";
const result = try finishThenElseBlock( const result = try finishThenElseBlock(
parent_gz, parent_gz,
@ -6760,7 +6831,7 @@ fn switchExpr(
} }
const block_ref = indexToRef(switch_block); const block_ref = indexToRef(switch_block);
if (strat.tag == .break_operand and strat.elide_store_to_block_ptr_instructions and rl != .ref) if (strat.tag == .break_operand and strat.elide_store_to_block_ptr_instructions and rl != .ref and rl != .catch_ref)
return rvalue(parent_gz, rl, block_ref, switch_node); return rvalue(parent_gz, rl, block_ref, switch_node);
return block_ref; return block_ref;
} }
@ -6839,19 +6910,12 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
.never => { .never => {
// Returning a value that cannot be an error; skip error defers. // Returning a value that cannot be an error; skip error defers.
try genDefers(gz, defer_outer, scope, .normal_only); try genDefers(gz, defer_outer, scope, .normal_only);
try emitDbgStmt(gz, ret_line, ret_column);
// As our last action before the return, "pop" the error trace if needed // As our last action before the return, "pop" the error trace if needed
if (gz.outermost_err_trace_index != .none) { if (gz.outermost_err_trace_index != .none)
_ = try gz.add(.{ _ = try gz.addUnNode(.restore_err_ret_index, gz.outermost_err_trace_index, node);
.tag = .restore_err_ret_index,
.data = .{ .un_node = .{
.operand = gz.outermost_err_trace_index,
.src_node = gz.nodeIndexToRelative(node),
} },
});
}
try emitDbgStmt(gz, ret_line, ret_column);
try gz.addRet(rl, operand, node); try gz.addRet(rl, operand, node);
return Zir.Inst.Ref.unreachable_value; return Zir.Inst.Ref.unreachable_value;
}, },
@ -6882,6 +6946,11 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
defer then_scope.unstack(); defer then_scope.unstack();
try genDefers(&then_scope, defer_outer, scope, .normal_only); try genDefers(&then_scope, defer_outer, scope, .normal_only);
// As our last action before the return, "pop" the error trace if needed
if (then_scope.outermost_err_trace_index != .none)
_ = try then_scope.addUnNode(.restore_err_ret_index, then_scope.outermost_err_trace_index, node);
try emitDbgStmt(&then_scope, ret_line, ret_column); try emitDbgStmt(&then_scope, ret_line, ret_column);
try then_scope.addRet(rl, operand, node); try then_scope.addRet(rl, operand, node);
@ -6893,17 +6962,6 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
}; };
try genDefers(&else_scope, defer_outer, scope, which_ones); try genDefers(&else_scope, defer_outer, scope, which_ones);
try emitDbgStmt(&else_scope, ret_line, ret_column); try emitDbgStmt(&else_scope, ret_line, ret_column);
// As our last action before the return, "pop" the error trace if needed
if (else_scope.outermost_err_trace_index != .none) {
_ = try else_scope.add(.{
.tag = .restore_err_ret_index,
.data = .{ .un_node = .{
.operand = else_scope.outermost_err_trace_index,
.src_node = else_scope.nodeIndexToRelative(node),
} },
});
}
try else_scope.addRet(rl, operand, node); try else_scope.addRet(rl, operand, node);
try setCondBrPayload(condbr, is_non_err, &then_scope, 0, &else_scope, 0); try setCondBrPayload(condbr, is_non_err, &then_scope, 0, &else_scope, 0);
@ -7068,7 +7126,7 @@ fn localVarRef(
); );
switch (rl) { switch (rl) {
.ref => return ptr_inst, .ref, .catch_ref => return ptr_inst,
else => { else => {
const loaded = try gz.addUnNode(.load, ptr_inst, ident); const loaded = try gz.addUnNode(.load, ptr_inst, ident);
return rvalue(gz, rl, loaded, ident); return rvalue(gz, rl, loaded, ident);
@ -7105,7 +7163,7 @@ fn localVarRef(
// Decl references happen by name rather than ZIR index so that when unrelated // Decl references happen by name rather than ZIR index so that when unrelated
// decls are modified, ZIR code containing references to them can be unmodified. // decls are modified, ZIR code containing references to them can be unmodified.
switch (rl) { switch (rl) {
.ref => return gz.addStrTok(.decl_ref, name_str_index, ident_token), .ref, .catch_ref => return gz.addStrTok(.decl_ref, name_str_index, ident_token),
else => { else => {
const result = try gz.addStrTok(.decl_val, name_str_index, ident_token); const result = try gz.addStrTok(.decl_val, name_str_index, ident_token);
return rvalue(gz, rl, result, ident); return rvalue(gz, rl, result, ident);
@ -7452,7 +7510,7 @@ fn as(
) InnerError!Zir.Inst.Ref { ) InnerError!Zir.Inst.Ref {
const dest_type = try typeExpr(gz, scope, lhs); const dest_type = try typeExpr(gz, scope, lhs);
switch (rl) { switch (rl) {
.none, .discard, .ref, .ty, .ty_shift_operand, .coerced_ty => { .none, .catch_none, .discard, .ref, .catch_ref, .ty, .ty_shift_operand, .coerced_ty => {
const result = try reachableExpr(gz, scope, .{ .ty = dest_type }, rhs, node); const result = try reachableExpr(gz, scope, .{ .ty = dest_type }, rhs, node);
return rvalue(gz, rl, result, node); return rvalue(gz, rl, result, node);
}, },
@ -7652,7 +7710,7 @@ fn builtinCall(
return rvalue(gz, rl, result, node); return rvalue(gz, rl, result, node);
}, },
.field => { .field => {
if (rl == .ref) { if (rl == .ref or rl == .catch_ref) {
return gz.addPlNode(.field_ptr_named, node, Zir.Inst.FieldNamed{ return gz.addPlNode(.field_ptr_named, node, Zir.Inst.FieldNamed{
.lhs = try expr(gz, scope, .ref, params[0]), .lhs = try expr(gz, scope, .ref, params[0]),
.field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]), .field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]),
@ -9600,13 +9658,13 @@ fn rvalue(
}; };
if (gz.endsWithNoReturn()) return result; if (gz.endsWithNoReturn()) return result;
switch (rl) { switch (rl) {
.none, .coerced_ty => return result, .none, .catch_none, .coerced_ty => return result,
.discard => { .discard => {
// Emit a compile error for discarding error values. // Emit a compile error for discarding error values.
_ = try gz.addUnNode(.ensure_result_non_error, result, src_node); _ = try gz.addUnNode(.ensure_result_non_error, result, src_node);
return result; return result;
}, },
.ref => { .ref, .catch_ref => {
// We need a pointer but we have a value. // We need a pointer but we have a value.
// Unfortunately it's not quite as simple as directly emitting a ref // Unfortunately it's not quite as simple as directly emitting a ref
// instruction here because we need subsequent address-of operator on // instruction here because we need subsequent address-of operator on
@ -10575,7 +10633,7 @@ const GenZir = struct {
gz.break_result_loc = parent_rl; gz.break_result_loc = parent_rl;
}, },
.discard, .none, .ref => { .discard, .none, .catch_none, .ref, .catch_ref => {
gz.rl_ty_inst = .none; gz.rl_ty_inst = .none;
gz.break_result_loc = parent_rl; gz.break_result_loc = parent_rl;
}, },

View file

@ -155,6 +155,59 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
}, },
}); });
cases.addCase(.{
.name = "catch and re-throw error",
.source =
\\fn foo() !void {
\\ return error.TheSkyIsFalling;
\\}
\\
\\pub fn main() !void {
\\ return foo() catch error.AndMyCarIsOutOfGas;
\\}
,
.Debug = .{
.expect =
\\error: AndMyCarIsOutOfGas
\\source.zig:2:5: [address] in foo (test)
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:6:5: [address] in main (test)
\\ return foo() catch error.AndMyCarIsOutOfGas;
\\ ^
\\
,
},
.ReleaseSafe = .{
.exclude_os = .{
.windows, // TODO
.linux, // defeated by aggressive inlining
},
.expect =
\\error: AndMyCarIsOutOfGas
\\source.zig:2:5: [address] in [function]
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:6:5: [address] in [function]
\\ return foo() catch error.AndMyCarIsOutOfGas;
\\ ^
\\
,
},
.ReleaseFast = .{
.expect =
\\error: AndMyCarIsOutOfGas
\\
,
},
.ReleaseSmall = .{
.expect =
\\error: AndMyCarIsOutOfGas
\\
,
},
});
cases.addCase(.{ cases.addCase(.{
.name = "try return from within catch", .name = "try return from within catch",
.source = .source =