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 {
782782 /// The length is given by `names_len`.
783783 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
785789 pub fn srcLoc(self: ErrorSet) SrcLoc {
786790 return .{
787791 .file_scope = self.owner_decl.getFileScope(),
src/Sema.zig+49-28
......@@ -4845,6 +4845,8 @@ fn funcCommon(
48454845 const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, .{
48464846 .func = new_func,
48474847 .map = .{},
4848 .functions = .{},
4849 .is_anyerror = false,
48484850 });
48494851 break :blk try Type.Tag.error_union.create(sema.arena, .{
48504852 .error_set = error_set_ty,
......@@ -8466,19 +8468,13 @@ fn zirRetErrValue(
84668468 const err_name = inst_data.get(sema.code);
84678469 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 }
84758471 // Return the error code from the function.
84768472 const kv = try sema.mod.getErrorValue(err_name);
84778473 const result_inst = try sema.addConstant(
84788474 try Type.Tag.error_set_single.create(sema.arena, kv.key),
84798475 try Value.Tag.@"error".create(sema.arena, .{ .name = kv.key }),
84808476 );
8481 return sema.analyzeRet(block, result_inst, src, true);
8477 return sema.analyzeRet(block, result_inst, src);
84828478}
84838479
84848480fn zirRetCoerce(
......@@ -8493,7 +8489,7 @@ fn zirRetCoerce(
84938489 const operand = sema.resolveInst(inst_data.operand);
84948490 const src = inst_data.src();
84958491
8496 return sema.analyzeRet(block, operand, src, true);
8492 return sema.analyzeRet(block, operand, src);
84978493}
84988494
84998495fn 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
85048500 const operand = sema.resolveInst(inst_data.operand);
85058501 const src = inst_data.src();
85068502
8507 // TODO: we pass false here for the `need_coercion` boolean, but I'm pretty sure we need
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);
8503 return sema.analyzeRet(block, operand, src);
85128504}
85138505
85148506fn 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
85218513
85228514 if (block.is_comptime or block.inlining != null) {
85238515 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);
85258517 }
85268518 try sema.requireRuntimeBlock(block, src);
85278519 _ = try block.addUnOp(.ret_load, ret_ptr);
......@@ -8533,12 +8525,25 @@ fn analyzeRet(
85338525 block: *Block,
85348526 uncasted_operand: Air.Inst.Ref,
85358527 src: LazySrcLoc,
8536 need_coercion: bool,
85378528) CompileError!Zir.Inst.Index {
8538 const operand = if (!need_coercion)
8539 uncasted_operand
8540 else
8541 try sema.coerce(block, sema.fn_ret_ty, uncasted_operand, src);
8529 // Special case for returning an error to an inferred error set; we need to
8530 // add the error tag to the inferred error set of the in-scope function, so
8531 // that the coercion below works correctly.
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
85438548 if (block.inlining) |inlining| {
85448549 if (block.is_comptime) {
......@@ -11605,14 +11610,30 @@ fn coerce(
1160511610 // T to E!T or E to E!T
1160611611 return sema.wrapErrorUnion(block, dest_ty, inst, inst_src);
1160711612 },
11608 .ErrorSet => {
11609 // Coercion to `anyerror`.
11610 // TODO If the dest type tag is not `anyerror` it still could
11611 // resolve to anyerror. `dest_ty` needs to have inferred error set resolution
11612 // happen before this check.
11613 if (dest_ty.tag() == .anyerror and inst_ty.zigTypeTag() == .ErrorSet) {
11614 return sema.coerceErrSetToAnyError(block, inst, inst_src);
11615 }
11613 .ErrorSet => switch (inst_ty.zigTypeTag()) {
11614 .ErrorSet => {
11615 // Coercion to `anyerror`. Note that this check can return false positives
11616 // in case the error sets did not get resolved.
11617 if (dest_ty.isAnyError()) {
11618 return sema.coerceCompatibleErrorSets(block, inst, inst_src);
11619 }
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 => {},
1161611637 },
1161711638 .Union => switch (inst_ty.zigTypeTag()) {
1161811639 .Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src),
......@@ -12245,7 +12266,7 @@ fn coerceVectorToArray(
1224512266 return block.addTyOp(.bitcast, array_ty, vector);
1224612267}
1224712268
12248fn coerceErrSetToAnyError(
12269fn coerceCompatibleErrorSets(
1224912270 sema: *Sema,
1225012271 block: *Block,
1225112272 err_set: Air.Inst.Ref,
src/codegen/llvm.zig+33-15
......@@ -2301,8 +2301,7 @@ pub const FuncGen = struct {
23012301 op: llvm.IntPredicate,
23022302 operand_is_ptr: bool,
23032303 ) !?*const llvm.Value {
2304 if (self.liveness.isUnused(inst))
2305 return null;
2304 if (self.liveness.isUnused(inst)) return null;
23062305
23072306 const un_op = self.air.instructions.items(.data)[inst].un_op;
23082307 const operand = try self.resolveInst(un_op);
......@@ -2363,22 +2362,16 @@ pub const FuncGen = struct {
23632362 inst: Air.Inst.Index,
23642363 operand_is_ptr: bool,
23652364 ) !?*const llvm.Value {
2366 if (self.liveness.isUnused(inst))
2367 return null;
2365 if (self.liveness.isUnused(inst)) return null;
23682366
23692367 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
23702368 const operand = try self.resolveInst(ty_op.operand);
23712369 const err_union_ty = self.air.typeOf(ty_op.operand);
23722370 const payload_ty = err_union_ty.errorUnionPayload();
2373
2374 if (!payload_ty.hasCodeGenBits()) {
2375 return null;
2376 }
2377
2378 if (operand_is_ptr) {
2371 if (!payload_ty.hasCodeGenBits()) return null;
2372 if (operand_is_ptr or isByRef(payload_ty)) {
23792373 return self.builder.buildStructGEP(operand, 1, "");
23802374 }
2381
23822375 return self.builder.buildExtractValue(operand, 1, "");
23832376 }
23842377
......@@ -2400,7 +2393,7 @@ pub const FuncGen = struct {
24002393 return self.builder.buildLoad(operand, "");
24012394 }
24022395
2403 if (operand_is_ptr) {
2396 if (operand_is_ptr or isByRef(payload_ty)) {
24042397 const err_field_ptr = self.builder.buildStructGEP(operand, 0, "");
24052398 return self.builder.buildLoad(err_field_ptr, "");
24062399 }
......@@ -2469,10 +2462,35 @@ pub const FuncGen = struct {
24692462 }
24702463
24712464 fn airWrapErrUnionErr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2472 if (self.liveness.isUnused(inst))
2473 return null;
2465 if (self.liveness.isUnused(inst)) 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;
24762494 }
24772495
24782496 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 {
26192619 };
26202620 }
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
26222633 /// Asserts the type is an array or vector.
26232634 pub fn arrayLen(ty: Type) u64 {
26242635 return switch (ty.tag()) {
......@@ -3871,10 +3882,39 @@ pub const Type = extern union {
38713882 pub const base_tag = Tag.error_set_inferred;
38723883
38733884 base: Payload = Payload{ .tag = base_tag },
3874 data: struct {
3885 data: Data,
3886
3887 pub const Data = struct {
38753888 func: *Module.Fn,
3889 /// Direct additions to the inferred error set via `return error.Foo;`.
38763890 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 };
38783918 };
38793919
38803920 pub const Pointer = struct {
test/behavior/error.zig+18
......@@ -31,3 +31,21 @@ test "empty error union" {
3131 const x = error{} || error{};
3232 _ = x;
3333}
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}