authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-10-01 12:00:17-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-10-01 12:00:17-07:00
log8e1421f19ef3daea9b8a414bd5783189bc292676
treecf642506120e82fed3de9c4a32a2232492d49b94
parentd8bfbbbf25984cfdc4d50a92569523a0a151d9e6
parent0b1ba6eb521a15b38b08f9923dd84329487d5755
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #17346 from Vexu/errSetCast

Sema: implement `@errSetCast` for error unions

16 files changed, 110 insertions(+), 66 deletions(-)

doc/langref.html.in+6-6
......@@ -6657,7 +6657,7 @@ test "coercion from homogenous tuple to array" {
66576657 <li>{#link|@alignCast#} - make a pointer have more alignment</li>
66586658 <li>{#link|@enumFromInt#} - obtain an enum value based on its integer tag value</li>
66596659 <li>{#link|@errorFromInt#} - obtain an error code based on its integer value</li>
6660 <li>{#link|@errSetCast#} - convert to a smaller error set</li>
6660 <li>{#link|@errorCast#} - convert to a smaller error set</li>
66616661 <li>{#link|@floatCast#} - convert a larger float to a smaller float</li>
66626662 <li>{#link|@floatFromInt#} - convert an integer to a float value</li>
66636663 <li>{#link|@intCast#} - convert between integer types</li>
......@@ -8410,10 +8410,10 @@ test "main" {
84108410 </p>
84118411 {#header_close#}
84128412
8413 {#header_open|@errSetCast#}
8414 <pre>{#syntax#}@errSetCast(value: anytype) anytype{#endsyntax#}</pre>
8413 {#header_open|@errorCast#}
8414 <pre>{#syntax#}@errorCast(value: anytype) anytype{#endsyntax#}</pre>
84158415 <p>
8416 Converts an error value from one error set to another error set. The return type is the
8416 Converts an error set or error union value from one error set to another error set. The return type is the
84178417 inferred result type. Attempting to convert an error which is not in the destination error
84188418 set results in safety-protected {#link|Undefined Behavior#}.
84198419 </p>
......@@ -10257,7 +10257,7 @@ const Set2 = error{
1025710257 C,
1025810258};
1025910259comptime {
10260 _ = @as(Set2, @errSetCast(Set1.B));
10260 _ = @as(Set2, @errorCast(Set1.B));
1026110261}
1026210262 {#code_end#}
1026310263 <p>At runtime:</p>
......@@ -10276,7 +10276,7 @@ pub fn main() void {
1027610276 foo(Set1.B);
1027710277}
1027810278fn foo(set1: Set1) void {
10279 const x: Set2 = @errSetCast(set1);
10279 const x: Set2 = @errorCast(set1);
1028010280 std.debug.print("value: {}\n", .{x});
1028110281}
1028210282 {#code_end#}
lib/std/child_process.zig+2-2
......@@ -446,7 +446,7 @@ pub const ChildProcess = struct {
446446 // has a value greater than 0
447447 if ((fd[0].revents & std.os.POLL.IN) != 0) {
448448 const err_int = try readIntFd(err_pipe[0]);
449 return @as(SpawnError, @errSetCast(@errorFromInt(err_int)));
449 return @as(SpawnError, @errorCast(@errorFromInt(err_int)));
450450 }
451451 } else {
452452 // Write maxInt(ErrInt) to the write end of the err_pipe. This is after
......@@ -459,7 +459,7 @@ pub const ChildProcess = struct {
459459 // Here we potentially return the fork child's error from the parent
460460 // pid.
461461 if (err_int != maxInt(ErrInt)) {
462 return @as(SpawnError, @errSetCast(@errorFromInt(err_int)));
462 return @as(SpawnError, @errorCast(@errorFromInt(err_int)));
463463 }
464464 }
465465 }
lib/std/os.zig+1-1
......@@ -5419,7 +5419,7 @@ pub fn dl_iterate_phdr(
54195419 }
54205420 }.callbackC, @as(?*anyopaque, @ptrFromInt(@intFromPtr(&context))))) {
54215421 0 => return,
5422 else => |err| return @as(Error, @errSetCast(@errorFromInt(@as(u16, @intCast(err))))), // TODO don't hardcode u16
5422 else => |err| return @as(Error, @errorCast(@errorFromInt(@as(u16, @intCast(err))))), // TODO don't hardcode u16
54235423 }
54245424 }
54255425
lib/std/zig/render.zig+3-1
......@@ -1444,7 +1444,7 @@ fn renderBuiltinCall(
14441444 const slice = tree.tokenSlice(builtin_token);
14451445 const rewrite_two_param_cast = params.len == 2 and for ([_][]const u8{
14461446 "@bitCast",
1447 "@errSetCast",
1447 "@errorCast",
14481448 "@floatCast",
14491449 "@intCast",
14501450 "@ptrCast",
......@@ -1505,6 +1505,8 @@ fn renderBuiltinCall(
15051505 try ais.writer().writeAll("@intFromPtr");
15061506 } else if (mem.eql(u8, slice, "@fabs")) {
15071507 try ais.writer().writeAll("@abs");
1508 } else if (mem.eql(u8, slice, "@errSetCast")) {
1509 try ais.writer().writeAll("@errorCast");
15081510 } else {
15091511 try renderToken(ais, tree, builtin_token, .none); // @name
15101512 }
src/AstGen.zig+3-3
......@@ -8454,11 +8454,11 @@ fn builtinCall(
84548454 });
84558455 return rvalue(gz, ri, result, node);
84568456 },
8457 .err_set_cast => {
8457 .error_cast => {
84588458 try emitDbgNode(gz, node);
84598459
8460 const result = try gz.addExtendedPayload(.err_set_cast, Zir.Inst.BinNode{
8461 .lhs = try ri.rl.resultTypeForCast(gz, node, "@errSetCast"),
8460 const result = try gz.addExtendedPayload(.error_cast, Zir.Inst.BinNode{
8461 .lhs = try ri.rl.resultTypeForCast(gz, node, "@errorCast"),
84628462 .rhs = try expr(gz, scope, .{ .rl = .none }, params[0]),
84638463 .node = gz.nodeIndexToRelative(node),
84648464 });
src/AstRlAnnotate.zig+1-1
......@@ -945,7 +945,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
945945 .float_cast,
946946 .int_cast,
947947 .truncate,
948 .err_set_cast,
948 .error_cast,
949949 .ptr_cast,
950950 .align_cast,
951951 .addrspace_cast,
src/BuiltinFn.zig+3-3
......@@ -43,7 +43,7 @@ pub const Tag = enum {
4343 error_name,
4444 error_return_trace,
4545 int_from_error,
46 err_set_cast,
46 error_cast,
4747 @"export",
4848 @"extern",
4949 fence,
......@@ -455,9 +455,9 @@ pub const list = list: {
455455 },
456456 },
457457 .{
458 "@errSetCast",
458 "@errorCast",
459459 .{
460 .tag = .err_set_cast,
460 .tag = .error_cast,
461461 .eval_to_error = .always,
462462 .param_count = 1,
463463 },
src/Sema.zig+47-21
......@@ -1252,7 +1252,7 @@ fn analyzeBodyInner(
12521252 .wasm_memory_size => try sema.zirWasmMemorySize( block, extended),
12531253 .wasm_memory_grow => try sema.zirWasmMemoryGrow( block, extended),
12541254 .prefetch => try sema.zirPrefetch( block, extended),
1255 .err_set_cast => try sema.zirErrSetCast( block, extended),
1255 .error_cast => try sema.zirErrorCast( block, extended),
12561256 .await_nosuspend => try sema.zirAwaitNosuspend( block, extended),
12571257 .select => try sema.zirSelect( block, extended),
12581258 .int_from_error => try sema.zirIntFromError( block, extended),
......@@ -21747,17 +21747,31 @@ fn ptrFromIntVal(
2174721747 };
2174821748}
2174921749
21750fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
21750fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
2175121751 const mod = sema.mod;
2175221752 const ip = &mod.intern_pool;
2175321753 const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
2175421754 const src = LazySrcLoc.nodeOffset(extra.node);
2175521755 const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
21756 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@errSetCast");
21756 const base_dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_opt, "@errorCast");
2175721757 const operand = try sema.resolveInst(extra.rhs);
21758 const operand_ty = sema.typeOf(operand);
21759 try sema.checkErrorSetType(block, src, dest_ty);
21760 try sema.checkErrorSetType(block, operand_src, operand_ty);
21758 const base_operand_ty = sema.typeOf(operand);
21759 const dest_tag = base_dest_ty.zigTypeTag(mod);
21760 const operand_tag = base_operand_ty.zigTypeTag(mod);
21761 if (dest_tag != operand_tag) {
21762 return sema.fail(block, src, "expected source and destination types to match, found '{s}' and '{s}'", .{
21763 @tagName(operand_tag), @tagName(dest_tag),
21764 });
21765 } else if (dest_tag != .ErrorSet and dest_tag != .ErrorUnion) {
21766 return sema.fail(block, src, "expected error set or error union type, found '{s}'", .{@tagName(dest_tag)});
21767 }
21768 const dest_ty, const operand_ty = if (dest_tag == .ErrorUnion) .{
21769 base_dest_ty.errorUnionSet(mod),
21770 base_operand_ty.errorUnionSet(mod),
21771 } else .{
21772 base_dest_ty,
21773 base_operand_ty,
21774 };
2176121775
2176221776 // operand must be defined since it can be an invalid error value
2176321777 const maybe_operand_val = try sema.resolveDefinedValue(block, operand_src, operand);
......@@ -21804,8 +21818,15 @@ fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
2180421818 }
2180521819
2180621820 if (maybe_operand_val) |val| {
21807 if (!dest_ty.isAnyError(mod)) {
21808 const error_name = mod.intern_pool.indexToKey(val.toIntern()).err.name;
21821 if (!dest_ty.isAnyError(mod)) check: {
21822 const operand_val = mod.intern_pool.indexToKey(val.toIntern());
21823 var error_name: InternPool.NullTerminatedString = undefined;
21824 if (dest_tag == .ErrorUnion) {
21825 if (operand_val.error_union.val != .err_name) break :check;
21826 error_name = operand_val.error_union.val.err_name;
21827 } else {
21828 error_name = operand_val.err.name;
21829 }
2180921830 if (!Type.errorSetHasFieldIp(ip, dest_ty.toIntern(), error_name)) {
2181021831 const msg = msg: {
2181121832 const msg = try sema.errMsg(
......@@ -21822,16 +21843,29 @@ fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
2182221843 }
2182321844 }
2182421845
21825 return Air.internedToRef((try mod.getCoerced(val, dest_ty)).toIntern());
21846 return Air.internedToRef((try mod.getCoerced(val, base_dest_ty)).toIntern());
2182621847 }
2182721848
2182821849 try sema.requireRuntimeBlock(block, src, operand_src);
2182921850 if (block.wantSafety() and !dest_ty.isAnyError(mod) and sema.mod.backendSupportsFeature(.error_set_has_value)) {
21830 const err_int_inst = try block.addBitCast(Type.err_int, operand);
21831 const ok = try block.addTyOp(.error_set_has_value, dest_ty, err_int_inst);
21832 try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
21851 if (dest_tag == .ErrorUnion) {
21852 const err_code = try sema.analyzeErrUnionCode(block, operand_src, operand);
21853 const err_int = try block.addBitCast(Type.err_int, err_code);
21854 const zero_u16 = Air.internedToRef(try mod.intern(.{
21855 .int = .{ .ty = .u16_type, .storage = .{ .u64 = 0 } },
21856 }));
21857
21858 const has_value = try block.addTyOp(.error_set_has_value, dest_ty, err_code);
21859 const is_zero = try block.addBinOp(.cmp_eq, err_int, zero_u16);
21860 const ok = try block.addBinOp(.bit_or, has_value, is_zero);
21861 try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
21862 } else {
21863 const err_int_inst = try block.addBitCast(Type.err_int, operand);
21864 const ok = try block.addTyOp(.error_set_has_value, dest_ty, err_int_inst);
21865 try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
21866 }
2183321867 }
21834 return block.addBitCast(dest_ty, operand);
21868 return block.addBitCast(base_dest_ty, operand);
2183521869}
2183621870
2183721871fn zirPtrCastFull(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
......@@ -22916,14 +22950,6 @@ fn checkIntOrVectorAllowComptime(
2291622950 }
2291722951}
2291822952
22919fn checkErrorSetType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!void {
22920 const mod = sema.mod;
22921 switch (ty.zigTypeTag(mod)) {
22922 .ErrorSet => return,
22923 else => return sema.fail(block, src, "expected error set type, found '{}'", .{ty.fmt(mod)}),
22924 }
22925}
22926
2292722953const SimdBinOp = struct {
2292822954 len: ?usize,
2292922955 /// Coerced to `result_ty`.
src/Zir.zig+2-2
......@@ -1997,9 +1997,9 @@ pub const Inst = struct {
19971997 /// Implements `@setCold`.
19981998 /// `operand` is payload index to `UnNode`.
19991999 set_cold,
2000 /// Implements the `@errSetCast` builtin.
2000 /// Implements the `@errorCast` builtin.
20012001 /// `operand` is payload index to `BinNode`. `lhs` is dest type, `rhs` is operand.
2002 err_set_cast,
2002 error_cast,
20032003 /// `operand` is payload index to `UnNode`.
20042004 await_nosuspend,
20052005 /// Implements `@breakpoint`.
src/print_zir.zig+1-1
......@@ -594,7 +594,7 @@ const Writer = struct {
594594
595595 .builtin_extern,
596596 .c_define,
597 .err_set_cast,
597 .error_cast,
598598 .wasm_memory_grow,
599599 .prefetch,
600600 .c_va_arg,
stage1/zig1.wasm
Binary files a/stage1/zig1.wasm and b/stage1/zig1.wasm differ
test/behavior/error.zig+18-2
......@@ -228,13 +228,29 @@ const Set1 = error{ A, B };
228228const Set2 = error{ A, C };
229229
230230fn testExplicitErrorSetCast(set1: Set1) !void {
231 var x = @as(Set2, @errSetCast(set1));
231 var x = @as(Set2, @errorCast(set1));
232232 try expect(@TypeOf(x) == Set2);
233 var y = @as(Set1, @errSetCast(x));
233 var y = @as(Set1, @errorCast(x));
234234 try expect(@TypeOf(y) == Set1);
235235 try expect(y == error.A);
236236}
237237
238test "@errorCast on error unions" {
239 const S = struct {
240 fn doTheTest() !void {
241 const casted: error{Bad}!i32 = @errorCast(retErrUnion());
242 try expect((try casted) == 1234);
243 }
244
245 fn retErrUnion() anyerror!i32 {
246 return 1234;
247 }
248 };
249
250 try S.doTheTest();
251 try comptime S.doTheTest();
252}
253
238254test "comptime test error for empty error set" {
239255 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
240256
test/cases/compile_errors/explicit_error_set_cast_known_at_comptime_violates_error_sets.zig+1-1
......@@ -2,7 +2,7 @@ const Set1 = error{ A, B };
22const Set2 = error{ A, C };
33comptime {
44 var x = Set1.B;
5 var y: Set2 = @errSetCast(x);
5 var y: Set2 = @errorCast(x);
66 _ = y;
77}
88
test/cases/compile_errors/int_to_err_non_global_invalid_number.zig+1-1
......@@ -8,7 +8,7 @@ const Set2 = error{
88};
99comptime {
1010 var x = @intFromError(Set1.B);
11 var y: Set2 = @errSetCast(@errorFromInt(x));
11 var y: Set2 = @errorCast(@errorFromInt(x));
1212 _ = y;
1313}
1414
test/cases/safety/@errSetCast error not present in destination.zig deleted-21
......@@ -1,21 +0,0 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
4 _ = stack_trace;
5 if (std.mem.eql(u8, message, "invalid error code")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
9}
10const Set1 = error{ A, B };
11const Set2 = error{ A, C };
12pub fn main() !void {
13 foo(Set1.B) catch {};
14 return error.TestFailed;
15}
16fn foo(set1: Set1) Set2 {
17 return @errSetCast(set1);
18}
19// run
20// backend=llvm
21// target=native
test/cases/safety/@errorCast error not present in destination.zig created+21
......@@ -0,0 +1,21 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
4 _ = stack_trace;
5 if (std.mem.eql(u8, message, "invalid error code")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
9}
10const Set1 = error{ A, B };
11const Set2 = error{ A, C };
12pub fn main() !void {
13 foo(Set1.B) catch {};
14 return error.TestFailed;
15}
16fn foo(set1: Set1) Set2 {
17 return @errorCast(set1);
18}
19// run
20// backend=llvm
21// target=native