authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-09-23 11:50:55-07:00
committergravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-10-21 11:22:49-07:00
logb529d8e48f2082c4e8df10d0ff26e2c7702bb693
tree4e38a63b1c75b1ae1db0660c918afa0fedb56bbe
parent77720e30aaead1c814f2714bd5a7ad7ad0fbc23e

stage2: Propagate error return trace into fn call

This change extends the "lifetime" of the error return trace associated with an error to include the duration of a function call it is passed to. This means that if a function returns an error, its return trace will include the error return trace for any error inputs. This is needed to support `testing.expectError` and similar functions. If a function returns a non-error, we have to clean up any error return traces created by error-able call arguments.

3 files changed, 247 insertions(+), 69 deletions(-)

src/AstGen.zig+6-4
......@@ -335,6 +335,8 @@ pub const ResultInfo = struct {
335335 error_handling_expr,
336336 /// The expression is the right-hand side of a shift operation.
337337 shift_op,
338 /// The expression is an argument in a function call.
339 fn_arg,
338340 /// No specific operator in particular.
339341 none,
340342 };
......@@ -5217,9 +5219,9 @@ fn popErrorReturnTrace(
52175219
52185220 const result_is_err = nodeMayEvalToError(tree, node);
52195221
5220 // If we are breaking to a try/catch/error-union-if/return, the error trace propagates.
5222 // If we are breaking to a try/catch/error-union-if/return or a function call, the error trace propagates.
52215223 const propagate_error_trace = switch (ri.ctx) {
5222 .error_handling_expr, .@"return" => true,
5224 .error_handling_expr, .@"return", .fn_arg => true,
52235225 else => false,
52245226 };
52255227
......@@ -8548,7 +8550,7 @@ fn callExpr(
85488550 defer arg_block.unstack();
85498551
85508552 // `call_inst` is reused to provide the param type.
8551 const arg_ref = try expr(&arg_block, &arg_block.base, .{ .rl = .{ .coerced_ty = call_inst } }, param_node);
8553 const arg_ref = try expr(&arg_block, &arg_block.base, .{ .rl = .{ .coerced_ty = call_inst }, .ctx = .fn_arg }, param_node);
85528554 _ = try arg_block.addBreak(.break_inline, call_index, arg_ref);
85538555
85548556 const body = arg_block.instructionsSlice();
......@@ -8562,7 +8564,7 @@ fn callExpr(
85628564 // If our result location is a try/catch/error-union-if/return, the error trace propagates.
85638565 // Otherwise, it should always be popped (handled in Sema).
85648566 const propagate_error_trace = switch (ri.ctx) {
8565 .error_handling_expr, .@"return" => true, // Propagate to try/catch/error-union-if and return
8567 .error_handling_expr, .@"return", .fn_arg => true, // Propagate to try/catch/error-union-if, return, and other function calls
85668568 else => false,
85678569 };
85688570
src/Sema.zig+172-65
......@@ -499,6 +499,25 @@ pub const Block = struct {
499499 return result_index;
500500 }
501501
502 /// Insert an instruction into the block at `index`. Moves all following
503 /// instructions forward in the block to make room. Operation is O(N).
504 pub fn insertInst(block: *Block, index: Air.Inst.Index, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
505 return Air.indexToRef(try block.insertInstAsIndex(index, inst));
506 }
507
508 pub fn insertInstAsIndex(block: *Block, index: Air.Inst.Index, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Index {
509 const sema = block.sema;
510 const gpa = sema.gpa;
511
512 try sema.air_instructions.ensureUnusedCapacity(gpa, 1);
513
514 const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);
515 sema.air_instructions.appendAssumeCapacity(inst);
516
517 try block.instructions.insert(gpa, index, result_index);
518 return result_index;
519 }
520
502521 fn addUnreachable(block: *Block, src: LazySrcLoc, safety_check: bool) !void {
503522 if (safety_check and block.wantSafety()) {
504523 _ = try block.sema.safetyPanic(block, src, .unreach);
......@@ -5648,6 +5667,85 @@ fn funcDeclSrc(sema: *Sema, block: *Block, src: LazySrcLoc, func_inst: Air.Inst.
56485667 return owner_decl.srcLoc();
56495668}
56505669
5670/// Add instructions to block to "pop" the error return trace.
5671/// If `operand` is provided, only pops if operand is non-error.
5672fn popErrorReturnTrace(
5673 sema: *Sema,
5674 block: *Block,
5675 src: LazySrcLoc,
5676 operand: ?Air.Inst.Ref,
5677 saved_error_trace_index: Air.Inst.Ref,
5678) CompileError!void {
5679 var is_non_error: ?bool = null;
5680 var is_non_error_inst: Air.Inst.Ref = undefined;
5681 if (operand) |op| {
5682 is_non_error_inst = try sema.analyzeIsNonErr(block, src, op);
5683 if (try sema.resolveDefinedValue(block, src, is_non_error_inst)) |cond_val|
5684 is_non_error = cond_val.toBool();
5685 } else is_non_error = true; // no operand means pop unconditionally
5686
5687 if (is_non_error == true) {
5688 // AstGen determined this result does not go to an error-handling expr (try/catch/return etc.), or
5689 // the result is comptime-known to be a non-error. Either way, pop unconditionally.
5690
5691 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
5692 const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
5693 const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);
5694 const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
5695 const field_ptr = try sema.structFieldPtr(block, src, err_return_trace, "index", src, stack_trace_ty, true);
5696 try sema.storePtr2(block, src, field_ptr, src, saved_error_trace_index, src, .store);
5697 } else if (is_non_error == null) {
5698 // The result might be an error. If it is, we leave the error trace alone. If it isn't, we need
5699 // to pop any error trace that may have been propagated from our arguments.
5700
5701 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Block).Struct.fields.len);
5702 const cond_block_inst = try block.addInstAsIndex(.{
5703 .tag = .block,
5704 .data = .{
5705 .ty_pl = .{
5706 .ty = Air.Inst.Ref.void_type,
5707 .payload = undefined, // updated below
5708 },
5709 },
5710 });
5711
5712 var then_block = block.makeSubBlock();
5713 defer then_block.instructions.deinit(sema.gpa);
5714
5715 // If non-error, then pop the error return trace by restoring the index.
5716 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
5717 const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
5718 const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);
5719 const err_return_trace = try then_block.addTy(.err_return_trace, ptr_stack_trace_ty);
5720 const field_ptr = try sema.structFieldPtr(&then_block, src, err_return_trace, "index", src, stack_trace_ty, true);
5721 try sema.storePtr2(&then_block, src, field_ptr, src, saved_error_trace_index, src, .store);
5722 _ = try then_block.addBr(cond_block_inst, Air.Inst.Ref.void_value);
5723
5724 // Otherwise, do nothing
5725 var else_block = block.makeSubBlock();
5726 defer else_block.instructions.deinit(sema.gpa);
5727 _ = try else_block.addBr(cond_block_inst, Air.Inst.Ref.void_value);
5728
5729 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.CondBr).Struct.fields.len +
5730 then_block.instructions.items.len + else_block.instructions.items.len +
5731 @typeInfo(Air.Block).Struct.fields.len + 1); // +1 for the sole .cond_br instruction in the .block
5732
5733 const cond_br_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
5734 try sema.air_instructions.append(sema.gpa, .{ .tag = .cond_br, .data = .{ .pl_op = .{
5735 .operand = is_non_error_inst,
5736 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
5737 .then_body_len = @intCast(u32, then_block.instructions.items.len),
5738 .else_body_len = @intCast(u32, else_block.instructions.items.len),
5739 }),
5740 } } });
5741 sema.air_extra.appendSliceAssumeCapacity(then_block.instructions.items);
5742 sema.air_extra.appendSliceAssumeCapacity(else_block.instructions.items);
5743
5744 sema.air_instructions.items(.data)[cond_block_inst].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
5745 sema.air_extra.appendAssumeCapacity(cond_br_inst);
5746 }
5747}
5748
56515749fn zirCall(
56525750 sema: *Sema,
56535751 block: *Block,
......@@ -5737,6 +5835,9 @@ fn zirCall(
57375835
57385836 const args_body = sema.code.extra[extra.end..];
57395837
5838 var input_is_error = false;
5839 const block_index = @intCast(Air.Inst.Index, block.instructions.items.len);
5840
57405841 const parent_comptime = block.is_comptime;
57415842 // `extra_index` and `arg_index` are separate since the bound function is passed as the first argument.
57425843 var extra_index: usize = 0;
......@@ -5754,10 +5855,8 @@ fn zirCall(
57545855 else
57555856 func_ty_info.param_types[arg_index];
57565857
5757 const old_comptime = block.is_comptime;
5758 defer block.is_comptime = old_comptime;
57595858 // Generate args to comptime params in comptime block.
5760 block.is_comptime = parent_comptime;
5859 defer block.is_comptime = parent_comptime;
57615860 if (arg_index < fn_params_len and func_ty_info.comptime_params[arg_index]) {
57625861 block.is_comptime = true;
57635862 }
......@@ -5766,13 +5865,58 @@ fn zirCall(
57665865 try sema.inst_map.put(sema.gpa, inst, param_ty_inst);
57675866
57685867 const resolved = try sema.resolveBody(block, args_body[arg_start..arg_end], inst);
5769 if (sema.typeOf(resolved).zigTypeTag() == .NoReturn) {
5868 const resolved_ty = sema.typeOf(resolved);
5869 if (resolved_ty.zigTypeTag() == .NoReturn) {
57705870 return resolved;
57715871 }
5872 if (resolved_ty.isError()) {
5873 input_is_error = true;
5874 }
57725875 resolved_args[arg_index] = resolved;
57735876 }
5877 if (sema.owner_func == null or !sema.owner_func.?.calls_or_awaits_errorable_fn)
5878 input_is_error = false; // input was an error type, but no errorable fn's were actually called
5879
5880 const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
5881 if (backend_supports_error_return_tracing and sema.mod.comp.bin_file.options.error_return_tracing and
5882 !block.is_comptime and (input_is_error or pop_error_return_trace))
5883 {
5884 const call_inst: Air.Inst.Ref = if (modifier == .always_tail) undefined else b: {
5885 break :b try sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
5886 };
5887
5888 const return_ty = sema.typeOf(call_inst);
5889 if (modifier != .always_tail and return_ty.isNoReturn())
5890 return call_inst; // call to "fn(...) noreturn", don't pop
5891
5892 // If any input is an error-type, we might need to pop any trace it generated. Otherwise, we only
5893 // need to clean-up our own trace if we were passed to a non-error-handling expression.
5894 if (input_is_error or (pop_error_return_trace and modifier != .always_tail and return_ty.isError())) {
5895 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, call_src, "StackTrace");
5896 const stack_trace_ty = try sema.resolveTypeFields(block, call_src, unresolved_stack_trace_ty);
5897 const field_index = try sema.structFieldIndex(block, stack_trace_ty, "index", call_src);
5898
5899 // Insert a save instruction before the arg resolution + call instructions we just generated
5900 const save_inst = try block.insertInst(block_index, .{
5901 .tag = .save_err_return_trace_index,
5902 .data = .{ .ty_pl = .{
5903 .ty = try sema.addType(stack_trace_ty),
5904 .payload = @intCast(u32, field_index),
5905 } },
5906 });
5907
5908 // Pop the error return trace, testing the result for non-error if necessary
5909 const operand = if (pop_error_return_trace or modifier == .always_tail) null else call_inst;
5910 try sema.popErrorReturnTrace(block, call_src, operand, save_inst);
5911 }
57745912
5775 return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, pop_error_return_trace, resolved_args, bound_arg_src);
5913 if (modifier == .always_tail) // Perform the call *after* the restore, so that a tail call is possible.
5914 return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
5915
5916 return call_inst;
5917 } else {
5918 return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
5919 }
57765920}
57775921
57785922const GenericCallAdapter = struct {
......@@ -5884,7 +6028,6 @@ fn analyzeCall(
58846028 call_src: LazySrcLoc,
58856029 modifier: std.builtin.CallOptions.Modifier,
58866030 ensure_result_used: bool,
5887 pop_error_return_trace: bool,
58886031 uncasted_args: []const Air.Inst.Ref,
58896032 bound_arg_src: ?LazySrcLoc,
58906033) CompileError!Air.Inst.Ref {
......@@ -6335,55 +6478,19 @@ fn analyzeCall(
63356478 sema.owner_func.?.calls_or_awaits_errorable_fn = true;
63366479 }
63376480
6338 const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
6339 const emit_error_trace_save_restore = sema.mod.comp.bin_file.options.error_return_tracing and
6340 backend_supports_error_return_tracing and
6341 pop_error_return_trace and func_ty_info.return_type.isError();
6342
6343 if (emit_error_trace_save_restore) {
6344 // This function call is error-able (and so can generate an error trace), but AstGen determined
6345 // that its result does not go to an error-handling operator (try/catch/return etc.). We need to
6346 // save and restore the error trace index here, effectively "popping" the new entries immediately.
6347
6348 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, call_src, "StackTrace");
6349 const stack_trace_ty = try sema.resolveTypeFields(block, call_src, unresolved_stack_trace_ty);
6350 const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);
6351 const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
6352 const field_ptr = try sema.structFieldPtr(block, call_src, err_return_trace, "index", call_src, stack_trace_ty, true);
6353
6354 const saved_index = try sema.analyzeLoad(block, call_src, field_ptr, call_src);
6355
6356 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +
6357 args.len);
6358 const func_inst = try block.addInst(.{
6359 .tag = call_tag,
6360 .data = .{ .pl_op = .{
6361 .operand = func,
6362 .payload = sema.addExtraAssumeCapacity(Air.Call{
6363 .args_len = @intCast(u32, args.len),
6364 }),
6365 } },
6366 });
6367 sema.appendRefsAssumeCapacity(args);
6368
6369 try sema.storePtr2(block, call_src, field_ptr, call_src, saved_index, call_src, .store);
6370
6371 break :res func_inst;
6372 } else {
6373 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +
6374 args.len);
6375 const func_inst = try block.addInst(.{
6376 .tag = call_tag,
6377 .data = .{ .pl_op = .{
6378 .operand = func,
6379 .payload = sema.addExtraAssumeCapacity(Air.Call{
6380 .args_len = @intCast(u32, args.len),
6381 }),
6382 } },
6383 });
6384 sema.appendRefsAssumeCapacity(args);
6385 break :res func_inst;
6386 }
6481 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +
6482 args.len);
6483 const func_inst = try block.addInst(.{
6484 .tag = call_tag,
6485 .data = .{ .pl_op = .{
6486 .operand = func,
6487 .payload = sema.addExtraAssumeCapacity(Air.Call{
6488 .args_len = @intCast(u32, args.len),
6489 }),
6490 } },
6491 });
6492 sema.appendRefsAssumeCapacity(args);
6493 break :res func_inst;
63876494 };
63886495
63896496 if (ensure_result_used) {
......@@ -10965,7 +11072,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
1096511072 const panic_fn = try sema.getBuiltin(block, src, "panicUnwrapError");
1096611073 const err_return_trace = try sema.getErrorReturnTrace(block, src);
1096711074 const args: [2]Air.Inst.Ref = .{ err_return_trace, operand };
10968 _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, false, &args, null);
11075 _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args, null);
1096911076 return true;
1097011077 },
1097111078 .panic => {
......@@ -10976,7 +11083,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
1097611083 const panic_fn = try sema.getBuiltin(block, src, "panic");
1097711084 const err_return_trace = try sema.getErrorReturnTrace(block, src);
1097811085 const args: [3]Air.Inst.Ref = .{ msg_inst, err_return_trace, .null_value };
10979 _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, false, &args, null);
11086 _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args, null);
1098011087 return true;
1098111088 },
1098211089 else => unreachable,
......@@ -16179,7 +16286,7 @@ fn retWithErrTracing(
1617916286 const args: [1]Air.Inst.Ref = .{err_return_trace};
1618016287
1618116288 if (!need_check) {
16182 _ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, false, &args, null);
16289 _ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, &args, null);
1618316290 _ = try block.addUnOp(ret_tag, operand);
1618416291 return always_noreturn;
1618516292 }
......@@ -16190,7 +16297,7 @@ fn retWithErrTracing(
1619016297
1619116298 var else_block = block.makeSubBlock();
1619216299 defer else_block.instructions.deinit(gpa);
16193 _ = try sema.analyzeCall(&else_block, return_err_fn, src, src, .never_inline, false, false, &args, null);
16300 _ = try sema.analyzeCall(&else_block, return_err_fn, src, src, .never_inline, false, &args, null);
1619416301 _ = try else_block.addUnOp(ret_tag, operand);
1619516302
1619616303 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +
......@@ -20414,7 +20521,7 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
2041420521 }
2041520522 }
2041620523 const ensure_result_used = extra.flags.ensure_result_used;
20417 return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, false, resolved_args, bound_arg_src);
20524 return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
2041820525}
2041920526
2042020527fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
......@@ -21848,7 +21955,7 @@ fn panicWithMsg(
2184821955 Value.@"null",
2184921956 );
2185021957 const args: [3]Air.Inst.Ref = .{ msg_inst, null_stack_trace, .null_value };
21851 _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, false, &args, null);
21958 _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args, null);
2185221959 return always_noreturn;
2185321960}
2185421961
......@@ -21889,7 +21996,7 @@ fn panicUnwrapError(
2188921996 const err = try fail_block.addTyOp(unwrap_err_tag, Type.anyerror, operand);
2189021997 const err_return_trace = try sema.getErrorReturnTrace(&fail_block, src);
2189121998 const args: [2]Air.Inst.Ref = .{ err_return_trace, err };
21892 _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, false, &args, null);
21999 _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args, null);
2189322000 }
2189422001 }
2189522002 try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
......@@ -21930,7 +22037,7 @@ fn panicIndexOutOfBounds(
2193022037 } else {
2193122038 const panic_fn = try sema.getBuiltin(&fail_block, src, "panicOutOfBounds");
2193222039 const args: [2]Air.Inst.Ref = .{ index, len };
21933 _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, false, &args, null);
22040 _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args, null);
2193422041 }
2193522042 }
2193622043 try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
......@@ -21972,7 +22079,7 @@ fn panicSentinelMismatch(
2197222079 else {
2197322080 const panic_fn = try sema.getBuiltin(parent_block, src, "checkNonScalarSentinel");
2197422081 const args: [2]Air.Inst.Ref = .{ expected_sentinel, actual_sentinel };
21975 _ = try sema.analyzeCall(parent_block, panic_fn, src, src, .auto, false, false, &args, null);
22082 _ = try sema.analyzeCall(parent_block, panic_fn, src, src, .auto, false, &args, null);
2197622083 return;
2197722084 };
2197822085 const gpa = sema.gpa;
......@@ -22001,7 +22108,7 @@ fn panicSentinelMismatch(
2200122108 } else {
2200222109 const panic_fn = try sema.getBuiltin(&fail_block, src, "panicSentinelMismatch");
2200322110 const args: [2]Air.Inst.Ref = .{ expected_sentinel, actual_sentinel };
22004 _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, false, &args, null);
22111 _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args, null);
2200522112 }
2200622113 }
2200722114 try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
test/stack_traces.zig+69
......@@ -260,6 +260,75 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
260260 },
261261 });
262262
263 cases.addCase(.{
264 .name = "error passed to function has its trace preserved for duration of the call",
265 .source =
266 \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {
267 \\ actual_error catch |err| {
268 \\ if (err == expected_error) return {};
269 \\ };
270 \\ return error.TestExpectedError;
271 \\}
272 \\
273 \\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; }
274 \\fn foo() !void { return error.Foo; }
275 \\
276 \\pub fn main() !void {
277 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
278 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
279 \\ try expectError(error.Foo, foo());
280 \\
281 \\ // Only the error trace for this failing check should appear:
282 \\ try expectError(error.Bar, foo());
283 \\}
284 ,
285 .Debug = .{
286 .expect =
287 \\error: TestExpectedError
288 \\source.zig:9:18: [address] in foo (test)
289 \\fn foo() !void { return error.Foo; }
290 \\ ^
291 \\source.zig:5:5: [address] in expectError (test)
292 \\ return error.TestExpectedError;
293 \\ ^
294 \\source.zig:17:5: [address] in main (test)
295 \\ try expectError(error.Bar, foo());
296 \\ ^
297 \\
298 ,
299 },
300 .ReleaseSafe = .{
301 .exclude_os = .{
302 .windows, // TODO
303 },
304 .expect =
305 \\error: TestExpectedError
306 \\source.zig:9:18: [address] in [function]
307 \\fn foo() !void { return error.Foo; }
308 \\ ^
309 \\source.zig:5:5: [address] in [function]
310 \\ return error.TestExpectedError;
311 \\ ^
312 \\source.zig:17:5: [address] in [function]
313 \\ try expectError(error.Bar, foo());
314 \\ ^
315 \\
316 ,
317 },
318 .ReleaseFast = .{
319 .expect =
320 \\error: TestExpectedError
321 \\
322 ,
323 },
324 .ReleaseSmall = .{
325 .expect =
326 \\error: TestExpectedError
327 \\
328 ,
329 },
330 });
331
263332 cases.addCase(.{
264333 .name = "try return from within catch",
265334 .source =