Merge pull request #12837 from topolarity/err-ret-trace-improvements-1923

stage2: Pop error trace frames for handled errors (#1923)
This commit is contained in:
Andrew Kelley 2022-10-21 20:24:37 -07:00 committed by GitHub
commit 09236d29b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 2084 additions and 858 deletions

View File

@ -869,8 +869,10 @@ pub noinline fn returnError(st: *StackTrace) void {
}
pub inline fn addErrRetTraceAddr(st: *StackTrace, addr: usize) void {
st.instruction_addresses[st.index & (st.instruction_addresses.len - 1)] = addr;
st.index +%= 1;
if (st.index < st.instruction_addresses.len)
st.instruction_addresses[st.index] = addr;
st.index += 1;
}
const std = @import("std.zig");

View File

@ -411,6 +411,14 @@ pub fn writeStackTrace(
const return_address = stack_trace.instruction_addresses[frame_index];
try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config);
}
if (stack_trace.index > stack_trace.instruction_addresses.len) {
const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len;
tty_config.setColor(out_stream, .Bold);
try out_stream.print("({d} additional stack frames skipped...)\n", .{dropped_frames});
tty_config.setColor(out_stream, .Reset);
}
}
pub const StackIterator = struct {

View File

@ -733,6 +733,10 @@ pub const Inst = struct {
/// Uses the `ty_op` field.
addrspace_cast,
/// Saves the error return trace index, if any. Otherwise, returns 0.
/// Uses the `ty_pl` field.
save_err_return_trace_index,
pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag {
switch (op) {
.lt => return if (optimized) .cmp_lt_optimized else .cmp_lt,
@ -1179,6 +1183,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.slice_len,
.ret_addr,
.frame_addr,
.save_err_return_trace_index,
=> return Type.usize,
.wasm_memory_grow => return Type.i32,

File diff suppressed because it is too large Load Diff

View File

@ -228,6 +228,7 @@ pub fn categorizeOperand(
.frame_addr,
.wasm_memory_size,
.err_return_trace,
.save_err_return_trace_index,
=> return .none,
.fence => return .write,
@ -805,6 +806,7 @@ fn analyzeInst(
.frame_addr,
.wasm_memory_size,
.err_return_trace,
.save_err_return_trace_index,
=> return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
.not,

View File

@ -5633,6 +5633,12 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {
const last_arg_index = inner_block.instructions.items.len;
// Save the error trace as our first action in the function.
// If this is unnecessary after all, Liveness will clean it up for us.
const error_return_trace_index = try sema.analyzeSaveErrRetIndex(&inner_block);
sema.error_return_trace_index_on_fn_entry = error_return_trace_index;
inner_block.error_return_trace_index = error_return_trace_index;
sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) {
// TODO make these unreachable instead of @panic
error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"),

View File

@ -32,6 +32,8 @@ owner_func: ?*Module.Fn,
/// This starts out the same as `owner_func` and then diverges in the case of
/// an inline or comptime function call.
func: ?*Module.Fn,
/// Used to restore the error return trace when returning a non-error from a function.
error_return_trace_index_on_fn_entry: Air.Inst.Ref = .none,
/// When semantic analysis needs to know the return type of the function whose body
/// is being analyzed, this `Type` should be used instead of going through `func`.
/// This will correctly handle the case of a comptime/inline function call of a
@ -153,6 +155,10 @@ pub const Block = struct {
is_typeof: bool = false,
is_coerce_result_ptr: bool = false,
/// Keep track of the active error return trace index around blocks so that we can correctly
/// pop the error trace upon block exit.
error_return_trace_index: Air.Inst.Ref = .none,
/// when null, it is determined by build mode, changed by @setRuntimeSafety
want_safety: ?bool = null,
@ -226,6 +232,7 @@ pub const Block = struct {
.float_mode = parent.float_mode,
.c_import_buf = parent.c_import_buf,
.switch_else_err_ty = parent.switch_else_err_ty,
.error_return_trace_index = parent.error_return_trace_index,
};
}
@ -499,6 +506,25 @@ pub const Block = struct {
return result_index;
}
/// Insert an instruction into the block at `index`. Moves all following
/// instructions forward in the block to make room. Operation is O(N).
pub fn insertInst(block: *Block, index: Air.Inst.Index, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
return Air.indexToRef(try block.insertInstAsIndex(index, inst));
}
pub fn insertInstAsIndex(block: *Block, index: Air.Inst.Index, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Index {
const sema = block.sema;
const gpa = sema.gpa;
try sema.air_instructions.ensureUnusedCapacity(gpa, 1);
const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);
sema.air_instructions.appendAssumeCapacity(inst);
try block.instructions.insert(gpa, index, result_index);
return result_index;
}
fn addUnreachable(block: *Block, src: LazySrcLoc, safety_check: bool) !void {
if (safety_check and block.wantSafety()) {
_ = try block.sema.safetyPanic(block, src, .unreach);
@ -1208,6 +1234,16 @@ fn analyzeBodyInner(
i += 1;
continue;
},
.save_err_ret_index => {
try sema.zirSaveErrRetIndex(block, inst);
i += 1;
continue;
},
.restore_err_ret_index => {
try sema.zirRestoreErrRetIndex(block, inst);
i += 1;
continue;
},
// Special case instructions to handle comptime control flow.
.@"break" => {
@ -1300,31 +1336,32 @@ fn analyzeBodyInner(
const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len];
const gpa = sema.gpa;
// If this block contains a function prototype, we need to reset the
// current list of parameters and restore it later.
// Note: this probably needs to be resolved in a more general manner.
const prev_params = block.params;
const need_sub_block = tags[inline_body[inline_body.len - 1]] == .repeat_inline;
var sub_block = block;
var block_space: Block = undefined;
// NOTE: this has to be done like this because branching in
// defers here breaks stage1.
block_space.instructions = .{};
if (need_sub_block) {
block_space = block.makeSubBlock();
block_space.inline_block = inline_body[0];
sub_block = &block_space;
}
block.params = .{};
defer {
block.params.deinit(gpa);
block.params = prev_params;
block_space.instructions.deinit(gpa);
}
const opt_break_data = try sema.analyzeBodyBreak(sub_block, inline_body);
if (need_sub_block) {
try block.instructions.appendSlice(gpa, block_space.instructions.items);
}
const opt_break_data = b: {
// Create a temporary child block so that this inline block is properly
// labeled for any .restore_err_ret_index instructions
var child_block = block.makeSubBlock();
// If this block contains a function prototype, we need to reset the
// current list of parameters and restore it later.
// Note: this probably needs to be resolved in a more general manner.
if (tags[inline_body[inline_body.len - 1]] == .repeat_inline) {
child_block.inline_block = inline_body[0];
} else child_block.inline_block = block.inline_block;
var label: Block.Label = .{
.zir_block = inst,
.merges = undefined,
};
child_block.label = &label;
defer child_block.params.deinit(gpa);
// Write these instructions directly into the parent block
child_block.instructions = block.instructions;
defer block.instructions = child_block.instructions;
break :b try sema.analyzeBodyBreak(&child_block, inline_body);
};
// A runtime conditional branch that needs a post-hoc block to be
// emitted communicates this by mapping the block index into the inst map.
@ -4968,7 +5005,7 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro
// Reserve space for a Block instruction so that generated Break instructions can
// point to it, even if it doesn't end up getting used because the code ends up being
// comptime evaluated.
// comptime evaluated or is an unlabeled block.
const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
try sema.air_instructions.append(gpa, .{
.tag = .block,
@ -4999,6 +5036,7 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro
.runtime_cond = parent_block.runtime_cond,
.runtime_loop = parent_block.runtime_loop,
.runtime_index = parent_block.runtime_index,
.error_return_trace_index = parent_block.error_return_trace_index,
};
defer child_block.instructions.deinit(gpa);
@ -5641,6 +5679,117 @@ fn funcDeclSrc(sema: *Sema, block: *Block, src: LazySrcLoc, func_inst: Air.Inst.
return owner_decl.srcLoc();
}
pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref {
const src = sema.src;
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
if (!backend_supports_error_return_tracing or !sema.mod.comp.bin_file.options.error_return_tracing)
return .none;
if (block.is_comptime)
return .none;
const unresolved_stack_trace_ty = sema.getBuiltinType(block, src, "StackTrace") catch |err| switch (err) {
error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
else => |e| return e,
};
const stack_trace_ty = sema.resolveTypeFields(block, src, unresolved_stack_trace_ty) catch |err| switch (err) {
error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
else => |e| return e,
};
const field_index = sema.structFieldIndex(block, stack_trace_ty, "index", src) catch |err| switch (err) {
error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
else => |e| return e,
};
return try block.addInst(.{
.tag = .save_err_return_trace_index,
.data = .{ .ty_pl = .{
.ty = try sema.addType(stack_trace_ty),
.payload = @intCast(u32, field_index),
} },
});
}
/// Add instructions to block to "pop" the error return trace.
/// If `operand` is provided, only pops if operand is non-error.
fn popErrorReturnTrace(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
saved_error_trace_index: Air.Inst.Ref,
) CompileError!void {
var is_non_error: ?bool = null;
var is_non_error_inst: Air.Inst.Ref = undefined;
if (operand != .none) {
is_non_error_inst = try sema.analyzeIsNonErr(block, src, operand);
if (try sema.resolveDefinedValue(block, src, is_non_error_inst)) |cond_val|
is_non_error = cond_val.toBool();
} else is_non_error = true; // no operand means pop unconditionally
if (is_non_error == true) {
// AstGen determined this result does not go to an error-handling expr (try/catch/return etc.), or
// the result is comptime-known to be a non-error. Either way, pop unconditionally.
const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);
const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
const field_ptr = try sema.structFieldPtr(block, src, err_return_trace, "index", src, stack_trace_ty, true);
try sema.storePtr2(block, src, field_ptr, src, saved_error_trace_index, src, .store);
} else if (is_non_error == null) {
// The result might be an error. If it is, we leave the error trace alone. If it isn't, we need
// to pop any error trace that may have been propagated from our arguments.
try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Block).Struct.fields.len);
const cond_block_inst = try block.addInstAsIndex(.{
.tag = .block,
.data = .{
.ty_pl = .{
.ty = Air.Inst.Ref.void_type,
.payload = undefined, // updated below
},
},
});
var then_block = block.makeSubBlock();
defer then_block.instructions.deinit(sema.gpa);
// If non-error, then pop the error return trace by restoring the index.
const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);
const err_return_trace = try then_block.addTy(.err_return_trace, ptr_stack_trace_ty);
const field_ptr = try sema.structFieldPtr(&then_block, src, err_return_trace, "index", src, stack_trace_ty, true);
try sema.storePtr2(&then_block, src, field_ptr, src, saved_error_trace_index, src, .store);
_ = try then_block.addBr(cond_block_inst, Air.Inst.Ref.void_value);
// Otherwise, do nothing
var else_block = block.makeSubBlock();
defer else_block.instructions.deinit(sema.gpa);
_ = try else_block.addBr(cond_block_inst, Air.Inst.Ref.void_value);
try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.CondBr).Struct.fields.len +
then_block.instructions.items.len + else_block.instructions.items.len +
@typeInfo(Air.Block).Struct.fields.len + 1); // +1 for the sole .cond_br instruction in the .block
const cond_br_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
try sema.air_instructions.append(sema.gpa, .{ .tag = .cond_br, .data = .{ .pl_op = .{
.operand = is_non_error_inst,
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(u32, then_block.instructions.items.len),
.else_body_len = @intCast(u32, else_block.instructions.items.len),
}),
} } });
sema.air_extra.appendSliceAssumeCapacity(then_block.instructions.items);
sema.air_extra.appendSliceAssumeCapacity(else_block.instructions.items);
sema.air_instructions.items(.data)[cond_block_inst].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
sema.air_extra.appendAssumeCapacity(cond_br_inst);
}
}
fn zirCall(
sema: *Sema,
block: *Block,
@ -5657,6 +5806,7 @@ fn zirCall(
const modifier = @intToEnum(std.builtin.CallOptions.Modifier, extra.data.flags.packed_modifier);
const ensure_result_used = extra.data.flags.ensure_result_used;
const pop_error_return_trace = extra.data.flags.pop_error_return_trace;
var func = try sema.resolveInst(extra.data.callee);
var resolved_args: []Air.Inst.Ref = undefined;
@ -5729,6 +5879,9 @@ fn zirCall(
const args_body = sema.code.extra[extra.end..];
var input_is_error = false;
const block_index = @intCast(Air.Inst.Index, block.instructions.items.len);
const parent_comptime = block.is_comptime;
// `extra_index` and `arg_index` are separate since the bound function is passed as the first argument.
var extra_index: usize = 0;
@ -5746,10 +5899,8 @@ fn zirCall(
else
func_ty_info.param_types[arg_index];
const old_comptime = block.is_comptime;
defer block.is_comptime = old_comptime;
// Generate args to comptime params in comptime block.
block.is_comptime = parent_comptime;
defer block.is_comptime = parent_comptime;
if (arg_index < fn_params_len and func_ty_info.comptime_params[arg_index]) {
block.is_comptime = true;
}
@ -5758,13 +5909,58 @@ fn zirCall(
try sema.inst_map.put(sema.gpa, inst, param_ty_inst);
const resolved = try sema.resolveBody(block, args_body[arg_start..arg_end], inst);
if (sema.typeOf(resolved).zigTypeTag() == .NoReturn) {
const resolved_ty = sema.typeOf(resolved);
if (resolved_ty.zigTypeTag() == .NoReturn) {
return resolved;
}
if (resolved_ty.isError()) {
input_is_error = true;
}
resolved_args[arg_index] = resolved;
}
if (sema.owner_func == null or !sema.owner_func.?.calls_or_awaits_errorable_fn)
input_is_error = false; // input was an error type, but no errorable fn's were actually called
return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
if (backend_supports_error_return_tracing and sema.mod.comp.bin_file.options.error_return_tracing and
!block.is_comptime and (input_is_error or pop_error_return_trace))
{
const call_inst: Air.Inst.Ref = if (modifier == .always_tail) undefined else b: {
break :b try sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
};
const return_ty = sema.typeOf(call_inst);
if (modifier != .always_tail and return_ty.isNoReturn())
return call_inst; // call to "fn(...) noreturn", don't pop
// If any input is an error-type, we might need to pop any trace it generated. Otherwise, we only
// need to clean-up our own trace if we were passed to a non-error-handling expression.
if (input_is_error or (pop_error_return_trace and modifier != .always_tail and return_ty.isError())) {
const unresolved_stack_trace_ty = try sema.getBuiltinType(block, call_src, "StackTrace");
const stack_trace_ty = try sema.resolveTypeFields(block, call_src, unresolved_stack_trace_ty);
const field_index = try sema.structFieldIndex(block, stack_trace_ty, "index", call_src);
// Insert a save instruction before the arg resolution + call instructions we just generated
const save_inst = try block.insertInst(block_index, .{
.tag = .save_err_return_trace_index,
.data = .{ .ty_pl = .{
.ty = try sema.addType(stack_trace_ty),
.payload = @intCast(u32, field_index),
} },
});
// Pop the error return trace, testing the result for non-error if necessary
const operand = if (pop_error_return_trace or modifier == .always_tail) .none else call_inst;
try sema.popErrorReturnTrace(block, call_src, operand, save_inst);
}
if (modifier == .always_tail) // Perform the call *after* the restore, so that a tail call is possible.
return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
return call_inst;
} else {
return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
}
}
const GenericCallAdapter = struct {
@ -6056,6 +6252,10 @@ fn analyzeCall(
sema.func = module_fn;
defer sema.func = parent_func;
const parent_err_ret_index = sema.error_return_trace_index_on_fn_entry;
sema.error_return_trace_index_on_fn_entry = block.error_return_trace_index;
defer sema.error_return_trace_index_on_fn_entry = parent_err_ret_index;
var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, fn_owner_decl.src_scope);
defer wip_captures.deinit();
@ -6069,6 +6269,7 @@ fn analyzeCall(
.label = null,
.inlining = &inlining,
.is_comptime = is_comptime_call,
.error_return_trace_index = block.error_return_trace_index,
};
const merges = &child_block.inlining.?.merges;
@ -6814,6 +7015,13 @@ fn instantiateGenericCall(
}
arg_i += 1;
}
// Save the error trace as our first action in the function.
// If this is unnecessary after all, Liveness will clean it up for us.
const error_return_trace_index = try sema.analyzeSaveErrRetIndex(&child_block);
child_sema.error_return_trace_index_on_fn_entry = error_return_trace_index;
child_block.error_return_trace_index = error_return_trace_index;
const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body, fn_info.param_body_inst) catch |err| {
// TODO look up the compile error that happened here and attach a note to it
// pointing here, at the generic instantiation callsite.
@ -9703,6 +9911,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
.defer_err_code,
.err_union_code,
.ret_err_value_code,
.restore_err_ret_index,
.is_non_err,
.condbr,
=> {},
@ -10005,6 +10214,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
.runtime_cond = block.runtime_cond,
.runtime_loop = block.runtime_loop,
.runtime_index = block.runtime_index,
.error_return_trace_index = block.error_return_trace_index,
};
const merges = &child_block.label.?.merges;
defer child_block.instructions.deinit(gpa);
@ -10888,6 +11098,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
const tags = sema.code.instructions.items(.tag);
for (body) |inst| {
switch (tags[inst]) {
.save_err_ret_index,
.dbg_block_begin,
.dbg_block_end,
.dbg_stmt,
@ -10910,6 +11121,10 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
try sema.zirDbgStmt(block, inst);
continue;
},
.save_err_ret_index => {
try sema.zirSaveErrRetIndex(block, inst);
continue;
},
.str => try sema.zirStr(block, inst),
.as_node => try sema.zirAsNode(block, inst),
.field_val => try sema.zirFieldVal(block, inst),
@ -10955,6 +11170,7 @@ fn maybeErrorUnwrapCondbr(sema: *Sema, block: *Block, body: []const Zir.Inst.Ind
return;
}
if (try sema.resolveDefinedValue(block, cond_src, err_operand)) |val| {
if (!operand_ty.isError()) return;
if (val.getError() == null) return;
try sema.maybeErrorUnwrapComptime(block, body, err_operand);
}
@ -15519,6 +15735,7 @@ fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
.is_comptime = false,
.is_typeof = true,
.want_safety = false,
.error_return_trace_index = block.error_return_trace_index,
};
defer child_block.instructions.deinit(sema.gpa);
@ -16176,6 +16393,75 @@ fn wantErrorReturnTracing(sema: *Sema, fn_ret_ty: Type) bool {
backend_supports_error_return_tracing;
}
fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[inst].save_err_ret_index;
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
const ok = backend_supports_error_return_tracing and sema.mod.comp.bin_file.options.error_return_tracing;
if (!ok) return;
// This is only relevant at runtime.
if (block.is_comptime) return;
// This is only relevant within functions.
if (sema.func == null) return;
const save_index = inst_data.operand == .none or b: {
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
break :b operand_ty.isError();
};
if (save_index)
block.error_return_trace_index = try sema.analyzeSaveErrRetIndex(block);
}
fn zirRestoreErrRetIndex(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[inst].restore_err_ret_index;
const src = sema.src; // TODO
// This is only relevant at runtime.
if (start_block.is_comptime) return;
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
const ok = sema.owner_func.?.calls_or_awaits_errorable_fn and
sema.mod.comp.bin_file.options.error_return_tracing and
backend_supports_error_return_tracing;
if (!ok) return;
const tracy = trace(@src());
defer tracy.end();
const saved_index = if (Zir.refToIndex(inst_data.block)) |zir_block| b: {
var block = start_block;
while (true) {
if (block.label) |label| {
if (label.zir_block == zir_block) {
const target_trace_index = if (block.parent) |parent_block| tgt: {
break :tgt parent_block.error_return_trace_index;
} else sema.error_return_trace_index_on_fn_entry;
if (start_block.error_return_trace_index != target_trace_index)
break :b target_trace_index;
return; // No need to restore
}
}
block = block.parent.?;
}
} else b: {
if (start_block.error_return_trace_index != sema.error_return_trace_index_on_fn_entry)
break :b sema.error_return_trace_index_on_fn_entry;
return; // No need to restore
};
assert(saved_index != .none); // The .error_return_trace_index field was dropped somewhere
const operand = try sema.resolveInst(inst_data.operand);
return sema.popErrorReturnTrace(start_block, src, operand, saved_index);
}
fn addToInferredErrorSet(sema: *Sema, uncasted_operand: Air.Inst.Ref) !void {
assert(sema.fn_ret_ty.zigTypeTag() == .ErrorUnion);
@ -17181,8 +17467,6 @@ fn zirBoolToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
fn zirErrorName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
const src = inst_data.src();
_ = src;
const operand = try sema.resolveInst(inst_data.operand);
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };

View File

@ -988,6 +988,15 @@ pub const Inst = struct {
/// Uses the `err_defer_code` union field.
defer_err_code,
/// Requests that Sema update the saved error return trace index for the enclosing
/// block, if the operand is .none or of an error/error-union type.
/// Uses the `save_err_ret_index` field.
save_err_ret_index,
/// Sets error return trace to zero if no operand is given,
/// otherwise sets the value to the given amount.
/// Uses the `restore_err_ret_index` union field.
restore_err_ret_index,
/// The ZIR instruction tag is one of the `Extended` ones.
/// Uses the `extended` union field.
extended,
@ -1236,6 +1245,8 @@ pub const Inst = struct {
//.try_ptr_inline,
.@"defer",
.defer_err_code,
.save_err_ret_index,
.restore_err_ret_index,
=> false,
.@"break",
@ -1305,6 +1316,8 @@ pub const Inst = struct {
.check_comptime_control_flow,
.@"defer",
.defer_err_code,
.restore_err_ret_index,
.save_err_ret_index,
=> true,
.param,
@ -1810,6 +1823,9 @@ pub const Inst = struct {
.@"defer" = .@"defer",
.defer_err_code = .defer_err_code,
.save_err_ret_index = .save_err_ret_index,
.restore_err_ret_index = .restore_err_ret_index,
.extended = .extended,
});
};
@ -2586,6 +2602,13 @@ pub const Inst = struct {
err_code: Ref,
payload_index: u32,
},
save_err_ret_index: struct {
operand: Ref, // If error type (or .none), save new trace index
},
restore_err_ret_index: struct {
block: Ref, // If restored, the index is from this block's entrypoint
operand: Ref, // If non-error (or .none), then restore the index
},
// Make sure we don't accidentally add a field to make this union
// bigger than expected. Note that in Debug builds, Zig is allowed
@ -2624,6 +2647,8 @@ pub const Inst = struct {
str_op,
@"defer",
defer_err_code,
save_err_ret_index,
restore_err_ret_index,
};
};
@ -2809,10 +2834,11 @@ pub const Inst = struct {
pub const Flags = packed struct {
/// std.builtin.CallOptions.Modifier in packed form
pub const PackedModifier = u3;
pub const PackedArgsLen = u28;
pub const PackedArgsLen = u27;
packed_modifier: PackedModifier,
ensure_result_used: bool = false,
pop_error_return_trace: bool,
args_len: PackedArgsLen,
comptime {

View File

@ -702,6 +702,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.save_err_return_trace_index=> try self.airSaveErrReturnTraceIndex(inst),
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -2867,6 +2868,11 @@ fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
}
fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
_ = inst;
return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
}
fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {

View File

@ -751,6 +751,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.save_err_return_trace_index=> try self.airSaveErrReturnTraceIndex(inst),
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -2116,6 +2117,11 @@ fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
}
fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
_ = inst;
return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
}
/// T to E!T
fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;

View File

@ -665,6 +665,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.save_err_return_trace_index=> try self.airSaveErrReturnTraceIndex(inst),
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -1329,6 +1330,11 @@ fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
}
fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
_ = inst;
return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
}
fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {

View File

@ -679,6 +679,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => @panic("TODO try self.airErrReturnTrace(inst)"),
.set_err_return_trace => @panic("TODO try self.airSetErrReturnTrace(inst)"),
.save_err_return_trace_index=> @panic("TODO try self.airSaveErrReturnTraceIndex(inst)"),
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => @panic("TODO try self.airWrapErrUnionPayload(inst)"),

View File

@ -1857,6 +1857,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.tag_name,
.err_return_trace,
.set_err_return_trace,
.save_err_return_trace_index,
.is_named_enum_value,
.error_set_has_value,
.addrspace_cast,

View File

@ -756,6 +756,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.save_err_return_trace_index=> try self.airSaveErrReturnTraceIndex(inst),
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -1973,6 +1974,11 @@ fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
}
fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
_ = inst;
return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
}
fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
if (self.liveness.isUnused(inst)) {

View File

@ -1935,6 +1935,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.errunion_payload_ptr_set => try airErrUnionPayloadPtrSet(f, inst),
.err_return_trace => try airErrReturnTrace(f, inst),
.set_err_return_trace => try airSetErrReturnTrace(f, inst),
.save_err_return_trace_index => try airSaveErrReturnTraceIndex(f, inst),
.wasm_memory_size => try airWasmMemorySize(f, inst),
.wasm_memory_grow => try airWasmMemoryGrow(f, inst),
@ -3625,6 +3626,11 @@ fn airSetErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue {
return f.fail("TODO: C backend: implement airSetErrReturnTrace", .{});
}
fn airSaveErrReturnTraceIndex(f: *Function, inst: Air.Inst.Index) !CValue {
_ = inst;
return f.fail("TODO: C backend: implement airSaveErrReturnTraceIndex", .{});
}
fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
if (f.liveness.isUnused(inst))
return CValue.none;

View File

@ -4592,6 +4592,7 @@ pub const FuncGen = struct {
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.save_err_return_trace_index => try self.airSaveErrReturnTraceIndex(inst),
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -6543,6 +6544,24 @@ pub const FuncGen = struct {
return null;
}
fn airSaveErrReturnTraceIndex(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const target = self.dg.module.getTarget();
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
//const struct_ty = try self.resolveInst(ty_pl.ty);
const struct_ty = self.air.getRefType(ty_pl.ty);
const field_index = ty_pl.payload;
var ptr_ty_buf: Type.Payload.Pointer = undefined;
const llvm_field_index = llvmFieldIndex(struct_ty, field_index, target, &ptr_ty_buf).?;
const struct_llvm_ty = try self.dg.lowerType(struct_ty);
const field_ptr = self.builder.buildStructGEP(struct_llvm_ty, self.err_ret_trace.?, llvm_field_index, "");
const field_ptr_ty = Type.initPayload(&ptr_ty_buf.base);
return self.load(field_ptr, field_ptr_ty);
}
fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
if (self.liveness.isUnused(inst)) return null;

View File

@ -197,6 +197,7 @@ const Writer = struct {
.unreach,
.ret_addr,
.frame_addr,
.save_err_return_trace_index,
=> try w.writeNoOp(s, inst),
.const_ty,

View File

@ -254,6 +254,9 @@ const Writer = struct {
.str => try self.writeStr(stream, inst),
.int_type => try self.writeIntType(stream, inst),
.save_err_ret_index => try self.writeSaveErrRetIndex(stream, inst),
.restore_err_ret_index => try self.writeRestoreErrRetIndex(stream, inst),
.@"break",
.break_inline,
=> try self.writeBreak(stream, inst),
@ -440,7 +443,7 @@ const Writer = struct {
.dbg_block_begin,
.dbg_block_end,
=> try stream.writeAll("))"),
=> try stream.writeAll(")"),
.closure_get => try self.writeInstNode(stream, inst),
@ -2272,6 +2275,22 @@ const Writer = struct {
try self.writeSrc(stream, int_type.src());
}
fn writeSaveErrRetIndex(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].save_err_ret_index;
try self.writeInstRef(stream, inst_data.operand);
try stream.writeAll(")");
}
fn writeRestoreErrRetIndex(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].restore_err_ret_index;
try self.writeInstRef(stream, inst_data.block);
try stream.writeAll(", ");
try self.writeInstRef(stream, inst_data.operand);
try stream.writeAll(")");
}
fn writeBreak(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].@"break";

View File

@ -2971,9 +2971,10 @@ pub const Value = extern union {
};
}
/// Valid for all types. Asserts the value is not undefined and not unreachable.
/// Prefer `errorUnionIsPayload` to find out whether something is an error or not
/// because it works without having to figure out the string.
/// Valid only for error (union) types. Asserts the value is not undefined and not
/// unreachable. For error unions, prefer `errorUnionIsPayload` to find out whether
/// something is an error or not because it works without having to figure out the
/// string.
pub fn getError(self: Value) ?[]const u8 {
return switch (self.tag()) {
.@"error" => self.castTag(.@"error").?.data.name,

View File

@ -7,6 +7,7 @@ test "issue12891" {
try std.testing.expect(i < f);
}
test "nan" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
const f = comptime std.math.nan(f64);

View File

@ -830,3 +830,16 @@ test "compare error union and error set" {
try expect(a != b);
try expect(b != a);
}
fn non_errorable() void {
// Make sure catch works even in a function that does not call any errorable functions.
//
// This test is needed because stage 2's fix for #1923 means that catch blocks interact
// with the error return trace index.
var x: error{Foo}!void = {};
return x catch {};
}
test "catch within a function that calls no errorable functions" {
non_errorable();
}

View File

@ -1401,7 +1401,21 @@ test "continue in inline for inside a comptime switch" {
try expect(count == 4);
}
test "length of global array is determinable at comptime" {
const S = struct {
var bytes: [1024]u8 = undefined;
fn foo() !void {
try std.testing.expect(bytes.len == 1024);
}
};
comptime try S.foo();
}
test "continue nested inline for loop" {
// TODO: https://github.com/ziglang/zig/issues/13175
if (builtin.zig_backend != .stage1) return error.SkipZigTest;
var a: u8 = 0;
loop: inline for ([_]u8{ 1, 2 }) |x| {
inline for ([_]u8{1}) |y| {
@ -1415,13 +1429,21 @@ test "continue nested inline for loop" {
try expect(a == 2);
}
test "length of global array is determinable at comptime" {
const S = struct {
var bytes: [1024]u8 = undefined;
test "continue nested inline for loop in named block expr" {
// TODO: https://github.com/ziglang/zig/issues/13175
if (builtin.zig_backend != .stage1) return error.SkipZigTest;
fn foo() !void {
try std.testing.expect(bytes.len == 1024);
}
};
comptime try S.foo();
var a: u8 = 0;
loop: inline for ([_]u8{ 1, 2 }) |x| {
a = b: {
inline for ([_]u8{1}) |y| {
if (x == y) {
continue :loop;
}
}
break :b x;
};
try expect(x == 2);
}
try expect(a == 2);
}

View File

@ -97,6 +97,547 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
,
},
});
cases.addCase(.{
.name = "non-error return pops error trace",
.source =
\\fn bar() !void {
\\ return error.UhOh;
\\}
\\
\\fn foo() !void {
\\ bar() catch {
\\ return; // non-error result: success
\\ };
\\}
\\
\\pub fn main() !void {
\\ try foo();
\\ return error.UnrelatedError;
\\}
,
.Debug = .{
.expect =
\\error: UnrelatedError
\\source.zig:13:5: [address] in main (test)
\\ return error.UnrelatedError;
\\ ^
\\
,
},
.ReleaseSafe = .{
.exclude_os = .{
.windows, // TODO
.linux, // defeated by aggressive inlining
},
.expect =
\\error: UnrelatedError
\\source.zig:13:5: [address] in [function]
\\ return error.UnrelatedError;
\\ ^
\\
,
},
.ReleaseFast = .{
.expect =
\\error: UnrelatedError
\\
,
},
.ReleaseSmall = .{
.expect =
\\error: UnrelatedError
\\
,
},
});
cases.addCase(.{
.name = "try return + handled catch/if-else",
.source =
\\fn foo() !void {
\\ return error.TheSkyIsFalling;
\\}
\\
\\pub fn main() !void {
\\ foo() catch {}; // should not affect error trace
\\ if (foo()) |_| {} else |_| {
\\ // should also not affect error trace
\\ }
\\ try foo();
\\}
,
.Debug = .{
.expect =
\\error: TheSkyIsFalling
\\source.zig:2:5: [address] in foo (test)
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:10:5: [address] in main (test)
\\ try foo();
\\ ^
\\
,
},
.ReleaseSafe = .{
.exclude_os = .{
.windows, // TODO
.linux, // defeated by aggressive inlining
},
.expect =
\\error: TheSkyIsFalling
\\source.zig:2:5: [address] in [function]
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:10:5: [address] in [function]
\\ try foo();
\\ ^
\\
,
},
.ReleaseFast = .{
.expect =
\\error: TheSkyIsFalling
\\
,
},
.ReleaseSmall = .{
.expect =
\\error: TheSkyIsFalling
\\
,
},
});
cases.addCase(.{
.name = "break from inline loop pops error return trace",
.source =
\\fn foo() !void { return error.FooBar; }
\\
\\pub fn main() !void {
\\ comptime var i: usize = 0;
\\ b: inline while (i < 5) : (i += 1) {
\\ foo() catch {
\\ break :b; // non-error break, success
\\ };
\\ }
\\ // foo() was successfully handled, should not appear in trace
\\
\\ return error.BadTime;
\\}
,
.Debug = .{
.expect =
\\error: BadTime
\\source.zig:12:5: [address] in main (test)
\\ return error.BadTime;
\\ ^
\\
,
},
.ReleaseSafe = .{
.exclude_os = .{
.windows, // TODO
.linux, // defeated by aggressive inlining
},
.expect =
\\error: BadTime
\\source.zig:12:5: [address] in [function]
\\ return error.BadTime;
\\ ^
\\
,
},
.ReleaseFast = .{
.expect =
\\error: BadTime
\\
,
},
.ReleaseSmall = .{
.expect =
\\error: BadTime
\\
,
},
});
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(.{
.name = "errors stored in var do not contribute to error trace",
.source =
\\fn foo() !void {
\\ return error.TheSkyIsFalling;
\\}
\\
\\pub fn main() !void {
\\ // Once an error is stored in a variable, it is popped from the trace
\\ var x = foo();
\\ x = {};
\\
\\ // As a result, this error trace will still be clean
\\ return error.SomethingUnrelatedWentWrong;
\\}
,
.Debug = .{
.expect =
\\error: SomethingUnrelatedWentWrong
\\source.zig:11:5: [address] in main (test)
\\ return error.SomethingUnrelatedWentWrong;
\\ ^
\\
,
},
.ReleaseSafe = .{
.exclude_os = .{
.windows, // TODO
.linux, // defeated by aggressive inlining
},
.expect =
\\error: SomethingUnrelatedWentWrong
\\source.zig:11:5: [address] in [function]
\\ return error.SomethingUnrelatedWentWrong;
\\ ^
\\
,
},
.ReleaseFast = .{
.expect =
\\error: SomethingUnrelatedWentWrong
\\
,
},
.ReleaseSmall = .{
.expect =
\\error: SomethingUnrelatedWentWrong
\\
,
},
});
cases.addCase(.{
.name = "error stored in const has trace preserved for duration of block",
.source =
\\fn foo() !void { return error.TheSkyIsFalling; }
\\fn bar() !void { return error.InternalError; }
\\fn baz() !void { return error.UnexpectedReality; }
\\
\\pub fn main() !void {
\\ const x = foo();
\\ const y = b: {
\\ if (true)
\\ break :b bar();
\\
\\ break :b {};
\\ };
\\ x catch {};
\\ y catch {};
\\ // foo()/bar() error traces not popped until end of block
\\
\\ {
\\ const z = baz();
\\ z catch {};
\\ // baz() error trace still alive here
\\ }
\\ // baz() error trace popped, foo(), bar() still alive
\\ return error.StillUnresolved;
\\}
,
.Debug = .{
.expect =
\\error: StillUnresolved
\\source.zig:1:18: [address] in foo (test)
\\fn foo() !void { return error.TheSkyIsFalling; }
\\ ^
\\source.zig:2:18: [address] in bar (test)
\\fn bar() !void { return error.InternalError; }
\\ ^
\\source.zig:23:5: [address] in main (test)
\\ return error.StillUnresolved;
\\ ^
\\
,
},
.ReleaseSafe = .{
.exclude_os = .{
.windows, // TODO
.linux, // defeated by aggressive inlining
},
.expect =
\\error: StillUnresolved
\\source.zig:1:18: [address] in [function]
\\fn foo() !void { return error.TheSkyIsFalling; }
\\ ^
\\source.zig:2:18: [address] in [function]
\\fn bar() !void { return error.InternalError; }
\\ ^
\\source.zig:23:5: [address] in [function]
\\ return error.StillUnresolved;
\\ ^
\\
,
},
.ReleaseFast = .{
.expect =
\\error: StillUnresolved
\\
,
},
.ReleaseSmall = .{
.expect =
\\error: StillUnresolved
\\
,
},
});
cases.addCase(.{
.name = "error passed to function has its trace preserved for duration of the call",
.source =
\\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {
\\ actual_error catch |err| {
\\ if (err == expected_error) return {};
\\ };
\\ return error.TestExpectedError;
\\}
\\
\\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; }
\\fn foo() !void { return error.Foo; }
\\
\\pub fn main() !void {
\\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
\\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
\\ try expectError(error.Foo, foo());
\\
\\ // Only the error trace for this failing check should appear:
\\ try expectError(error.Bar, foo());
\\}
,
.Debug = .{
.expect =
\\error: TestExpectedError
\\source.zig:9:18: [address] in foo (test)
\\fn foo() !void { return error.Foo; }
\\ ^
\\source.zig:5:5: [address] in expectError (test)
\\ return error.TestExpectedError;
\\ ^
\\source.zig:17:5: [address] in main (test)
\\ try expectError(error.Bar, foo());
\\ ^
\\
,
},
.ReleaseSafe = .{
.exclude_os = .{
.windows, // TODO
},
.expect =
\\error: TestExpectedError
\\source.zig:9:18: [address] in [function]
\\fn foo() !void { return error.Foo; }
\\ ^
\\source.zig:5:5: [address] in [function]
\\ return error.TestExpectedError;
\\ ^
\\source.zig:17:5: [address] in [function]
\\ try expectError(error.Bar, foo());
\\ ^
\\
,
},
.ReleaseFast = .{
.expect =
\\error: TestExpectedError
\\
,
},
.ReleaseSmall = .{
.expect =
\\error: TestExpectedError
\\
,
},
});
cases.addCase(.{
.name = "try return from within catch",
.source =
\\fn foo() !void {
\\ return error.TheSkyIsFalling;
\\}
\\
\\fn bar() !void {
\\ return error.AndMyCarIsOutOfGas;
\\}
\\
\\pub fn main() !void {
\\ foo() catch { // error trace should include foo()
\\ try bar();
\\ };
\\}
,
.Debug = .{
.expect =
\\error: AndMyCarIsOutOfGas
\\source.zig:2:5: [address] in foo (test)
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:6:5: [address] in bar (test)
\\ return error.AndMyCarIsOutOfGas;
\\ ^
\\source.zig:11:9: [address] in main (test)
\\ try bar();
\\ ^
\\
,
},
.ReleaseSafe = .{
.exclude_os = .{
.windows, // TODO
},
.expect =
\\error: AndMyCarIsOutOfGas
\\source.zig:2:5: [address] in [function]
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:6:5: [address] in [function]
\\ return error.AndMyCarIsOutOfGas;
\\ ^
\\source.zig:11:9: [address] in [function]
\\ try bar();
\\ ^
\\
,
},
.ReleaseFast = .{
.expect =
\\error: AndMyCarIsOutOfGas
\\
,
},
.ReleaseSmall = .{
.expect =
\\error: AndMyCarIsOutOfGas
\\
,
},
});
cases.addCase(.{
.name = "try return from within if-else",
.source =
\\fn foo() !void {
\\ return error.TheSkyIsFalling;
\\}
\\
\\fn bar() !void {
\\ return error.AndMyCarIsOutOfGas;
\\}
\\
\\pub fn main() !void {
\\ if (foo()) |_| {} else |_| { // error trace should include foo()
\\ try bar();
\\ }
\\}
,
.Debug = .{
.expect =
\\error: AndMyCarIsOutOfGas
\\source.zig:2:5: [address] in foo (test)
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:6:5: [address] in bar (test)
\\ return error.AndMyCarIsOutOfGas;
\\ ^
\\source.zig:11:9: [address] in main (test)
\\ try bar();
\\ ^
\\
,
},
.ReleaseSafe = .{
.exclude_os = .{
.windows, // TODO
},
.expect =
\\error: AndMyCarIsOutOfGas
\\source.zig:2:5: [address] in [function]
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:6:5: [address] in [function]
\\ return error.AndMyCarIsOutOfGas;
\\ ^
\\source.zig:11:9: [address] in [function]
\\ try bar();
\\ ^
\\
,
},
.ReleaseFast = .{
.expect =
\\error: AndMyCarIsOutOfGas
\\
,
},
.ReleaseSmall = .{
.expect =
\\error: AndMyCarIsOutOfGas
\\
,
},
});
cases.addCase(.{
.name = "try try return return",