authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-17 14:50:47-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-17 14:55:32-07:00
log6534f2ef4f8161f4121326f19bc3cf89324f62c5
tree87fb9d6f2b082a534402080f53febf483d81e7fc
parent53b87fa78a3ee2c261f3bdd6a71d5733fe17ffd5

stage2: implement error wrapping

* Sema: fix returned operands not coercing to the function return type in some cases. - When returning an error or an error union from a function with an inferred error set, it will now populate the inferred error set. - Implement error set coercion for the common case of inferred error set to inferred error set, without forcing a full resolution. * LLVM backend: update instruction lowering that handles error unions to respect `isByRef`. - Also implement `wrap_err_union_err`.

5 files changed, 146 insertions(+), 45 deletions(-)

src/Module.zig+4
...@@ -782,6 +782,10 @@ pub const ErrorSet = struct {...@@ -782,6 +782,10 @@ pub const ErrorSet = struct {
782 /// The length is given by `names_len`.782 /// The length is given by `names_len`.
783 names_ptr: [*]const []const u8,783 names_ptr: [*]const []const u8,
784784
785 pub fn names(self: ErrorSet) []const []const u8 {
786 return self.names_ptr[0..self.names_len];
787 }
788
785 pub fn srcLoc(self: ErrorSet) SrcLoc {789 pub fn srcLoc(self: ErrorSet) SrcLoc {
786 return .{790 return .{
787 .file_scope = self.owner_decl.getFileScope(),791 .file_scope = self.owner_decl.getFileScope(),
src/Sema.zig+49-28
...@@ -4845,6 +4845,8 @@ fn funcCommon(...@@ -4845,6 +4845,8 @@ fn funcCommon(
4845 const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, .{4845 const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, .{
4846 .func = new_func,4846 .func = new_func,
4847 .map = .{},4847 .map = .{},
4848 .functions = .{},
4849 .is_anyerror = false,
4848 });4850 });
4849 break :blk try Type.Tag.error_union.create(sema.arena, .{4851 break :blk try Type.Tag.error_union.create(sema.arena, .{
4850 .error_set = error_set_ty,4852 .error_set = error_set_ty,
...@@ -8466,19 +8468,13 @@ fn zirRetErrValue(...@@ -8466,19 +8468,13 @@ fn zirRetErrValue(
8466 const err_name = inst_data.get(sema.code);8468 const err_name = inst_data.get(sema.code);
8467 const src = inst_data.src();8469 const src = inst_data.src();
84688470
8469 // Add the error tag to the inferred error set of the in-scope function.
8470 if (sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) {
8471 if (sema.fn_ret_ty.errorUnionSet().castTag(.error_set_inferred)) |payload| {
8472 _ = try payload.data.map.getOrPut(sema.gpa, err_name);
8473 }
8474 }
8475 // Return the error code from the function.8471 // Return the error code from the function.
8476 const kv = try sema.mod.getErrorValue(err_name);8472 const kv = try sema.mod.getErrorValue(err_name);
8477 const result_inst = try sema.addConstant(8473 const result_inst = try sema.addConstant(
8478 try Type.Tag.error_set_single.create(sema.arena, kv.key),8474 try Type.Tag.error_set_single.create(sema.arena, kv.key),
8479 try Value.Tag.@"error".create(sema.arena, .{ .name = kv.key }),8475 try Value.Tag.@"error".create(sema.arena, .{ .name = kv.key }),
8480 );8476 );
8481 return sema.analyzeRet(block, result_inst, src, true);8477 return sema.analyzeRet(block, result_inst, src);
8482}8478}
84838479
8484fn zirRetCoerce(8480fn zirRetCoerce(
...@@ -8493,7 +8489,7 @@ fn zirRetCoerce(...@@ -8493,7 +8489,7 @@ fn zirRetCoerce(
8493 const operand = sema.resolveInst(inst_data.operand);8489 const operand = sema.resolveInst(inst_data.operand);
8494 const src = inst_data.src();8490 const src = inst_data.src();
84958491
8496 return sema.analyzeRet(block, operand, src, true);8492 return sema.analyzeRet(block, operand, src);
8497}8493}
84988494
8499fn zirRetNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {8495fn zirRetNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {
...@@ -8504,11 +8500,7 @@ fn zirRetNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir...@@ -8504,11 +8500,7 @@ fn zirRetNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir
8504 const operand = sema.resolveInst(inst_data.operand);8500 const operand = sema.resolveInst(inst_data.operand);
8505 const src = inst_data.src();8501 const src = inst_data.src();
85068502
8507 // TODO: we pass false here for the `need_coercion` boolean, but I'm pretty sure we need8503 return sema.analyzeRet(block, operand, src);
8508 // to remove this parameter entirely. Observe the problem by looking at the incorrect compile
8509 // error that occurs when a behavior test case being executed at comptime fails, e.g.
8510 // `test { comptime foo(); } fn foo() { try expect(false); }`
8511 return sema.analyzeRet(block, operand, src, false);
8512}8504}
85138505
8514fn zirRetLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {8506fn zirRetLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {
...@@ -8521,7 +8513,7 @@ fn zirRetLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir...@@ -8521,7 +8513,7 @@ fn zirRetLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir
85218513
8522 if (block.is_comptime or block.inlining != null) {8514 if (block.is_comptime or block.inlining != null) {
8523 const operand = try sema.analyzeLoad(block, src, ret_ptr, src);8515 const operand = try sema.analyzeLoad(block, src, ret_ptr, src);
8524 return sema.analyzeRet(block, operand, src, false);8516 return sema.analyzeRet(block, operand, src);
8525 }8517 }
8526 try sema.requireRuntimeBlock(block, src);8518 try sema.requireRuntimeBlock(block, src);
8527 _ = try block.addUnOp(.ret_load, ret_ptr);8519 _ = try block.addUnOp(.ret_load, ret_ptr);
...@@ -8533,12 +8525,25 @@ fn analyzeRet(...@@ -8533,12 +8525,25 @@ fn analyzeRet(
8533 block: *Block,8525 block: *Block,
8534 uncasted_operand: Air.Inst.Ref,8526 uncasted_operand: Air.Inst.Ref,
8535 src: LazySrcLoc,8527 src: LazySrcLoc,
8536 need_coercion: bool,
8537) CompileError!Zir.Inst.Index {8528) CompileError!Zir.Inst.Index {
8538 const operand = if (!need_coercion)8529 // Special case for returning an error to an inferred error set; we need to
8539 uncasted_operand8530 // add the error tag to the inferred error set of the in-scope function, so
8540 else8531 // that the coercion below works correctly.
8541 try sema.coerce(block, sema.fn_ret_ty, uncasted_operand, src);8532 if (sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) {
8533 if (sema.fn_ret_ty.errorUnionSet().castTag(.error_set_inferred)) |payload| {
8534 const op_ty = sema.typeOf(uncasted_operand);
8535 switch (op_ty.zigTypeTag()) {
8536 .ErrorSet => {
8537 try payload.data.addErrorSet(sema.gpa, op_ty);
8538 },
8539 .ErrorUnion => {
8540 try payload.data.addErrorSet(sema.gpa, op_ty.errorUnionSet());
8541 },
8542 else => {},
8543 }
8544 }
8545 }
8546 const operand = try sema.coerce(block, sema.fn_ret_ty, uncasted_operand, src);
85428547
8543 if (block.inlining) |inlining| {8548 if (block.inlining) |inlining| {
8544 if (block.is_comptime) {8549 if (block.is_comptime) {
...@@ -11605,14 +11610,30 @@ fn coerce(...@@ -11605,14 +11610,30 @@ fn coerce(
11605 // T to E!T or E to E!T11610 // T to E!T or E to E!T
11606 return sema.wrapErrorUnion(block, dest_ty, inst, inst_src);11611 return sema.wrapErrorUnion(block, dest_ty, inst, inst_src);
11607 },11612 },
11608 .ErrorSet => {11613 .ErrorSet => switch (inst_ty.zigTypeTag()) {
11609 // Coercion to `anyerror`.11614 .ErrorSet => {
11610 // TODO If the dest type tag is not `anyerror` it still could11615 // Coercion to `anyerror`. Note that this check can return false positives
11611 // resolve to anyerror. `dest_ty` needs to have inferred error set resolution11616 // in case the error sets did not get resolved.
11612 // happen before this check.11617 if (dest_ty.isAnyError()) {
11613 if (dest_ty.tag() == .anyerror and inst_ty.zigTypeTag() == .ErrorSet) {11618 return sema.coerceCompatibleErrorSets(block, inst, inst_src);
11614 return sema.coerceErrSetToAnyError(block, inst, inst_src);11619 }
11615 }11620 // If both are inferred error sets of functions, and
11621 // the dest includes the source function, the coercion is OK.
11622 // This check is important because it works without forcing a full resolution
11623 // of inferred error sets.
11624 if (inst_ty.castTag(.error_set_inferred)) |src_payload| {
11625 if (dest_ty.castTag(.error_set_inferred)) |dst_payload| {
11626 const src_func = src_payload.data.func;
11627 const dst_func = dst_payload.data.func;
11628
11629 if (src_func == dst_func or dst_payload.data.functions.contains(src_func)) {
11630 return sema.coerceCompatibleErrorSets(block, inst, inst_src);
11631 }
11632 }
11633 }
11634 // TODO full error set resolution and compare sets by names.
11635 },
11636 else => {},
11616 },11637 },
11617 .Union => switch (inst_ty.zigTypeTag()) {11638 .Union => switch (inst_ty.zigTypeTag()) {
11618 .Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src),11639 .Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src),
...@@ -12245,7 +12266,7 @@ fn coerceVectorToArray(...@@ -12245,7 +12266,7 @@ fn coerceVectorToArray(
12245 return block.addTyOp(.bitcast, array_ty, vector);12266 return block.addTyOp(.bitcast, array_ty, vector);
12246}12267}
1224712268
12248fn coerceErrSetToAnyError(12269fn coerceCompatibleErrorSets(
12249 sema: *Sema,12270 sema: *Sema,
12250 block: *Block,12271 block: *Block,
12251 err_set: Air.Inst.Ref,12272 err_set: Air.Inst.Ref,
src/codegen/llvm.zig+33-15
...@@ -2301,8 +2301,7 @@ pub const FuncGen = struct {...@@ -2301,8 +2301,7 @@ pub const FuncGen = struct {
2301 op: llvm.IntPredicate,2301 op: llvm.IntPredicate,
2302 operand_is_ptr: bool,2302 operand_is_ptr: bool,
2303 ) !?*const llvm.Value {2303 ) !?*const llvm.Value {
2304 if (self.liveness.isUnused(inst))2304 if (self.liveness.isUnused(inst)) return null;
2305 return null;
23062305
2307 const un_op = self.air.instructions.items(.data)[inst].un_op;2306 const un_op = self.air.instructions.items(.data)[inst].un_op;
2308 const operand = try self.resolveInst(un_op);2307 const operand = try self.resolveInst(un_op);
...@@ -2363,22 +2362,16 @@ pub const FuncGen = struct {...@@ -2363,22 +2362,16 @@ pub const FuncGen = struct {
2363 inst: Air.Inst.Index,2362 inst: Air.Inst.Index,
2364 operand_is_ptr: bool,2363 operand_is_ptr: bool,
2365 ) !?*const llvm.Value {2364 ) !?*const llvm.Value {
2366 if (self.liveness.isUnused(inst))2365 if (self.liveness.isUnused(inst)) return null;
2367 return null;
23682366
2369 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2367 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2370 const operand = try self.resolveInst(ty_op.operand);2368 const operand = try self.resolveInst(ty_op.operand);
2371 const err_union_ty = self.air.typeOf(ty_op.operand);2369 const err_union_ty = self.air.typeOf(ty_op.operand);
2372 const payload_ty = err_union_ty.errorUnionPayload();2370 const payload_ty = err_union_ty.errorUnionPayload();
23732371 if (!payload_ty.hasCodeGenBits()) return null;
2374 if (!payload_ty.hasCodeGenBits()) {2372 if (operand_is_ptr or isByRef(payload_ty)) {
2375 return null;
2376 }
2377
2378 if (operand_is_ptr) {
2379 return self.builder.buildStructGEP(operand, 1, "");2373 return self.builder.buildStructGEP(operand, 1, "");
2380 }2374 }
2381
2382 return self.builder.buildExtractValue(operand, 1, "");2375 return self.builder.buildExtractValue(operand, 1, "");
2383 }2376 }
23842377
...@@ -2400,7 +2393,7 @@ pub const FuncGen = struct {...@@ -2400,7 +2393,7 @@ pub const FuncGen = struct {
2400 return self.builder.buildLoad(operand, "");2393 return self.builder.buildLoad(operand, "");
2401 }2394 }
24022395
2403 if (operand_is_ptr) {2396 if (operand_is_ptr or isByRef(payload_ty)) {
2404 const err_field_ptr = self.builder.buildStructGEP(operand, 0, "");2397 const err_field_ptr = self.builder.buildStructGEP(operand, 0, "");
2405 return self.builder.buildLoad(err_field_ptr, "");2398 return self.builder.buildLoad(err_field_ptr, "");
2406 }2399 }
...@@ -2469,10 +2462,35 @@ pub const FuncGen = struct {...@@ -2469,10 +2462,35 @@ pub const FuncGen = struct {
2469 }2462 }
24702463
2471 fn airWrapErrUnionErr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {2464 fn airWrapErrUnionErr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2472 if (self.liveness.isUnused(inst))2465 if (self.liveness.isUnused(inst)) return null;
2473 return null;2466
2467 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2468 const err_un_ty = self.air.typeOfIndex(inst);
2469 const payload_ty = err_un_ty.errorUnionPayload();
2470 const operand = try self.resolveInst(ty_op.operand);
2471 if (!payload_ty.hasCodeGenBits()) {
2472 return operand;
2473 }
2474 const err_un_llvm_ty = try self.dg.llvmType(err_un_ty);
2475 if (isByRef(err_un_ty)) {
2476 const result_ptr = self.buildAlloca(err_un_llvm_ty);
2477 const err_ptr = self.builder.buildStructGEP(result_ptr, 0, "");
2478 _ = self.builder.buildStore(operand, err_ptr);
2479 const payload_ptr = self.builder.buildStructGEP(result_ptr, 1, "");
2480 var ptr_ty_payload: Type.Payload.ElemType = .{
2481 .base = .{ .tag = .single_mut_pointer },
2482 .data = payload_ty,
2483 };
2484 const payload_ptr_ty = Type.initPayload(&ptr_ty_payload.base);
2485 // TODO store undef to payload_ptr
2486 _ = payload_ptr;
2487 _ = payload_ptr_ty;
2488 return result_ptr;
2489 }
24742490
2475 return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});2491 const partial = self.builder.buildInsertValue(err_un_llvm_ty.getUndef(), operand, 0, "");
2492 // TODO set payload bytes to undef
2493 return partial;
2476 }2494 }
24772495
2478 fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {2496 fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
src/type.zig+42-2
...@@ -2619,6 +2619,17 @@ pub const Type = extern union {...@@ -2619,6 +2619,17 @@ pub const Type = extern union {
2619 };2619 };
2620 }2620 }
26212621
2622 /// Returns true if it is an error set that includes anyerror, false otherwise.
2623 /// Note that the result may be a false negative if the type did not get error set
2624 /// resolution prior to this call.
2625 pub fn isAnyError(ty: Type) bool {
2626 return switch (ty.tag()) {
2627 .anyerror => true,
2628 .error_set_inferred => ty.castTag(.error_set_inferred).?.data.is_anyerror,
2629 else => false,
2630 };
2631 }
2632
2622 /// Asserts the type is an array or vector.2633 /// Asserts the type is an array or vector.
2623 pub fn arrayLen(ty: Type) u64 {2634 pub fn arrayLen(ty: Type) u64 {
2624 return switch (ty.tag()) {2635 return switch (ty.tag()) {
...@@ -3871,10 +3882,39 @@ pub const Type = extern union {...@@ -3871,10 +3882,39 @@ pub const Type = extern union {
3871 pub const base_tag = Tag.error_set_inferred;3882 pub const base_tag = Tag.error_set_inferred;
38723883
3873 base: Payload = Payload{ .tag = base_tag },3884 base: Payload = Payload{ .tag = base_tag },
3874 data: struct {3885 data: Data,
3886
3887 pub const Data = struct {
3875 func: *Module.Fn,3888 func: *Module.Fn,
3889 /// Direct additions to the inferred error set via `return error.Foo;`.
3876 map: std.StringHashMapUnmanaged(void),3890 map: std.StringHashMapUnmanaged(void),
3877 },3891 /// Other functions with inferred error sets which this error set includes.
3892 functions: std.AutoHashMapUnmanaged(*Module.Fn, void),
3893 is_anyerror: bool,
3894
3895 pub fn addErrorSet(self: *Data, gpa: *Allocator, err_set_ty: Type) !void {
3896 switch (err_set_ty.tag()) {
3897 .error_set => {
3898 const names = err_set_ty.castTag(.error_set).?.data.names();
3899 for (names) |name| {
3900 try self.map.put(gpa, name, {});
3901 }
3902 },
3903 .error_set_single => {
3904 const name = err_set_ty.castTag(.error_set_single).?.data;
3905 try self.map.put(gpa, name, {});
3906 },
3907 .error_set_inferred => {
3908 const func = err_set_ty.castTag(.error_set_inferred).?.data.func;
3909 try self.functions.put(gpa, func, {});
3910 },
3911 .anyerror => {
3912 self.is_anyerror = true;
3913 },
3914 else => unreachable,
3915 }
3916 }
3917 };
3878 };3918 };
38793919
3880 pub const Pointer = struct {3920 pub const Pointer = struct {
test/behavior/error.zig+18
...@@ -31,3 +31,21 @@ test "empty error union" {...@@ -31,3 +31,21 @@ test "empty error union" {
31 const x = error{} || error{};31 const x = error{} || error{};
32 _ = x;32 _ = x;
33}33}
34
35pub fn foo() anyerror!i32 {
36 const x = try bar();
37 return x + 1;
38}
39
40pub fn bar() anyerror!i32 {
41 return 13;
42}
43
44pub fn baz() anyerror!i32 {
45 const y = foo() catch 1234;
46 return y + 1;
47}
48
49test "error wrapping" {
50 try expect((baz() catch unreachable) == 15);
51}