authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-02-05 21:06:39+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-02-05 21:17:40+00:00
log5317d88414324c6555338e8574811c6710df4e44
tree2ddf46e60b07970fe8586634f158d43f4bc0feea
parentfbbf34e563a376ea1654dce827b9194ba7211b3a
signature Commit is signed but in an unrecognized format.

Sema: fix `@errorCast` with error unions

Resolves: #20169

2 files changed, 100 insertions(+), 58 deletions(-)

src/Sema.zig+82-55
...@@ -23175,11 +23175,12 @@ fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData...@@ -23175,11 +23175,12 @@ fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData
23175 const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;23175 const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
23176 const src = block.nodeOffset(extra.node);23176 const src = block.nodeOffset(extra.node);
23177 const operand_src = block.builtinCallArgSrc(extra.node, 0);23177 const operand_src = block.builtinCallArgSrc(extra.node, 0);
23178 const base_dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_opt, "@errorCast");23178 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_opt, "@errorCast");
23179 const operand = try sema.resolveInst(extra.rhs);23179 const operand = try sema.resolveInst(extra.rhs);
23180 const base_operand_ty = sema.typeOf(operand);23180 const operand_ty = sema.typeOf(operand);
23181 const dest_tag = base_dest_ty.zigTypeTag(zcu);23181
23182 const operand_tag = base_operand_ty.zigTypeTag(zcu);23182 const dest_tag = dest_ty.zigTypeTag(zcu);
23183 const operand_tag = operand_ty.zigTypeTag(zcu);
2318323184
23184 if (dest_tag != .error_set and dest_tag != .error_union) {23185 if (dest_tag != .error_set and dest_tag != .error_union) {
23185 return sema.fail(block, src, "expected error set or error union type, found '{s}'", .{@tagName(dest_tag)});23186 return sema.fail(block, src, "expected error set or error union type, found '{s}'", .{@tagName(dest_tag)});
...@@ -23191,107 +23192,133 @@ fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData...@@ -23191,107 +23192,133 @@ fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData
23191 return sema.fail(block, src, "cannot cast an error union type to error set", .{});23192 return sema.fail(block, src, "cannot cast an error union type to error set", .{});
23192 }23193 }
23193 if (dest_tag == .error_union and operand_tag == .error_union and23194 if (dest_tag == .error_union and operand_tag == .error_union and
23194 base_dest_ty.errorUnionPayload(zcu).toIntern() != base_operand_ty.errorUnionPayload(zcu).toIntern())23195 dest_ty.errorUnionPayload(zcu).toIntern() != operand_ty.errorUnionPayload(zcu).toIntern())
23195 {23196 {
23196 return sema.failWithOwnedErrorMsg(block, msg: {23197 return sema.failWithOwnedErrorMsg(block, msg: {
23197 const msg = try sema.errMsg(src, "payload types of error unions must match", .{});23198 const msg = try sema.errMsg(src, "payload types of error unions must match", .{});
23198 errdefer msg.destroy(sema.gpa);23199 errdefer msg.destroy(sema.gpa);
23199 const dest_ty = base_dest_ty.errorUnionPayload(zcu);23200 const dest_payload_ty = dest_ty.errorUnionPayload(zcu);
23200 const operand_ty = base_operand_ty.errorUnionPayload(zcu);23201 const operand_payload_ty = operand_ty.errorUnionPayload(zcu);
23201 try sema.errNote(src, msg, "destination payload is '{}'", .{dest_ty.fmt(pt)});23202 try sema.errNote(src, msg, "destination payload is '{}'", .{dest_payload_ty.fmt(pt)});
23202 try sema.errNote(src, msg, "operand payload is '{}'", .{operand_ty.fmt(pt)});23203 try sema.errNote(src, msg, "operand payload is '{}'", .{operand_payload_ty.fmt(pt)});
23203 try addDeclaredHereNote(sema, msg, dest_ty);23204 try addDeclaredHereNote(sema, msg, dest_ty);
23204 try addDeclaredHereNote(sema, msg, operand_ty);23205 try addDeclaredHereNote(sema, msg, operand_ty);
23205 break :msg msg;23206 break :msg msg;
23206 });23207 });
23207 }23208 }
23208 const dest_ty = if (dest_tag == .error_union) base_dest_ty.errorUnionSet(zcu) else base_dest_ty;23209 const dest_err_ty = switch (dest_tag) {
23209 const operand_ty = if (operand_tag == .error_union) base_operand_ty.errorUnionSet(zcu) else base_operand_ty;23210 .error_union => dest_ty.errorUnionSet(zcu),
2321023211 .error_set => dest_ty,
23211 // operand must be defined since it can be an invalid error value23212 else => unreachable,
23212 const maybe_operand_val = try sema.resolveDefinedValue(block, operand_src, operand);23213 };
23214 const operand_err_ty = switch (operand_tag) {
23215 .error_union => operand_ty.errorUnionSet(zcu),
23216 .error_set => operand_ty,
23217 else => unreachable,
23218 };
2321323219
23214 const disjoint = disjoint: {23220 const disjoint = disjoint: {
23215 // Try avoiding resolving inferred error sets if we can23221 // Try avoiding resolving inferred error sets if we can
23216 if (!dest_ty.isAnyError(zcu) and dest_ty.errorSetIsEmpty(zcu)) break :disjoint true;23222 if (!dest_err_ty.isAnyError(zcu) and dest_err_ty.errorSetIsEmpty(zcu)) break :disjoint true;
23217 if (!operand_ty.isAnyError(zcu) and operand_ty.errorSetIsEmpty(zcu)) break :disjoint true;23223 if (!operand_err_ty.isAnyError(zcu) and operand_err_ty.errorSetIsEmpty(zcu)) break :disjoint true;
23218 if (dest_ty.isAnyError(zcu)) break :disjoint false;23224 if (dest_err_ty.isAnyError(zcu)) break :disjoint false;
23219 if (operand_ty.isAnyError(zcu)) break :disjoint false;23225 if (operand_err_ty.isAnyError(zcu)) break :disjoint false;
23220 const dest_err_names = dest_ty.errorSetNames(zcu);23226 const dest_err_names = dest_err_ty.errorSetNames(zcu);
23221 for (0..dest_err_names.len) |dest_err_index| {23227 for (0..dest_err_names.len) |dest_err_index| {
23222 if (Type.errorSetHasFieldIp(ip, operand_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))23228 if (Type.errorSetHasFieldIp(ip, operand_err_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))
23223 break :disjoint false;23229 break :disjoint false;
23224 }23230 }
2322523231
23226 if (!ip.isInferredErrorSetType(dest_ty.toIntern()) and23232 if (!ip.isInferredErrorSetType(dest_err_ty.toIntern()) and
23227 !ip.isInferredErrorSetType(operand_ty.toIntern()))23233 !ip.isInferredErrorSetType(operand_err_ty.toIntern()))
23228 {23234 {
23229 break :disjoint true;23235 break :disjoint true;
23230 }23236 }
2323123237
23232 _ = try sema.resolveInferredErrorSetTy(block, src, dest_ty.toIntern());23238 _ = try sema.resolveInferredErrorSetTy(block, src, dest_err_ty.toIntern());
23233 _ = try sema.resolveInferredErrorSetTy(block, operand_src, operand_ty.toIntern());23239 _ = try sema.resolveInferredErrorSetTy(block, operand_src, operand_err_ty.toIntern());
23234 for (0..dest_err_names.len) |dest_err_index| {23240 for (0..dest_err_names.len) |dest_err_index| {
23235 if (Type.errorSetHasFieldIp(ip, operand_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))23241 if (Type.errorSetHasFieldIp(ip, operand_err_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))
23236 break :disjoint false;23242 break :disjoint false;
23237 }23243 }
2323823244
23239 break :disjoint true;23245 break :disjoint true;
23240 };23246 };
23241 if (disjoint and dest_tag != .error_union) {23247 if (disjoint and !(operand_tag == .error_union and dest_tag == .error_union)) {
23242 return sema.fail(block, src, "error sets '{}' and '{}' have no common errors", .{23248 return sema.fail(block, src, "error sets '{}' and '{}' have no common errors", .{
23243 operand_ty.fmt(pt), dest_ty.fmt(pt),23249 operand_err_ty.fmt(pt), dest_err_ty.fmt(pt),
23244 });23250 });
23245 }23251 }
2324623252
23247 if (maybe_operand_val) |val| {23253 // operand must be defined since it can be an invalid error value
23248 if (!dest_ty.isAnyError(zcu)) check: {23254 if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| {
23249 const operand_val = zcu.intern_pool.indexToKey(val.toIntern());23255 const err_name: InternPool.NullTerminatedString = switch (operand_tag) {
23250 var error_name: InternPool.NullTerminatedString = undefined;23256 .error_set => ip.indexToKey(operand_val.toIntern()).err.name,
23251 if (operand_tag == .error_union) {23257 .error_union => switch (ip.indexToKey(operand_val.toIntern()).error_union.val) {
23252 if (operand_val.error_union.val != .err_name) break :check;23258 .err_name => |name| name,
23253 error_name = operand_val.error_union.val.err_name;23259 .payload => |payload_val| {
23254 } else {23260 assert(dest_tag == .error_union); // should be guaranteed from the type checks above
23255 error_name = operand_val.err.name;23261 return sema.coerce(block, dest_ty, Air.internedToRef(payload_val), operand_src);
23256 }23262 },
23257 if (!Type.errorSetHasFieldIp(ip, dest_ty.toIntern(), error_name)) {23263 },
23258 return sema.fail(block, src, "'error.{}' not a member of error set '{}'", .{23264 else => unreachable,
23259 error_name.fmt(ip), dest_ty.fmt(pt),23265 };
23260 });23266
23261 }23267 if (!dest_err_ty.isAnyError(zcu) and !Type.errorSetHasFieldIp(ip, dest_err_ty.toIntern(), err_name)) {
23268 return sema.fail(block, src, "'error.{}' not a member of error set '{}'", .{
23269 err_name.fmt(ip), dest_err_ty.fmt(pt),
23270 });
23262 }23271 }
2326323272
23264 return Air.internedToRef((try pt.getCoerced(val, base_dest_ty)).toIntern());23273 return Air.internedToRef(try pt.intern(switch (dest_tag) {
23274 .error_set => .{ .err = .{
23275 .ty = dest_ty.toIntern(),
23276 .name = err_name,
23277 } },
23278 .error_union => .{ .error_union = .{
23279 .ty = dest_ty.toIntern(),
23280 .val = .{ .err_name = err_name },
23281 } },
23282 else => unreachable,
23283 }));
23265 }23284 }
2326623285
23267 try sema.requireRuntimeBlock(block, src, operand_src);
23268 const err_int_ty = try pt.errorIntType();23286 const err_int_ty = try pt.errorIntType();
23269 if (block.wantSafety() and !dest_ty.isAnyError(zcu) and23287 if (block.wantSafety() and !dest_err_ty.isAnyError(zcu) and
23270 dest_ty.toIntern() != .adhoc_inferred_error_set_type and23288 dest_err_ty.toIntern() != .adhoc_inferred_error_set_type and
23271 zcu.backendSupportsFeature(.error_set_has_value))23289 zcu.backendSupportsFeature(.error_set_has_value))
23272 {23290 {
23273 if (dest_tag == .error_union) {23291 const err_code_inst = switch (operand_tag) {
23274 const err_code = try block.addTyOp(.unwrap_errunion_err, operand_ty, operand);23292 .error_set => operand,
23275 const err_int = try block.addBitCast(err_int_ty, err_code);23293 .error_union => try block.addTyOp(.unwrap_errunion_err, operand_err_ty, operand),
23276 const zero_err = try pt.intRef(try pt.errorIntType(), 0);23294 else => unreachable,
23295 };
23296 const err_int_inst = try block.addBitCast(err_int_ty, err_code_inst);
2327723297
23278 const is_zero = try block.addBinOp(.cmp_eq, err_int, zero_err);23298 if (dest_tag == .error_union) {
23299 const zero_err = try pt.intRef(err_int_ty, 0);
23300 const is_zero = try block.addBinOp(.cmp_eq, err_int_inst, zero_err);
23279 if (disjoint) {23301 if (disjoint) {
23280 // Error must be zero.23302 // Error must be zero.
23281 try sema.addSafetyCheck(block, src, is_zero, .invalid_error_code);23303 try sema.addSafetyCheck(block, src, is_zero, .invalid_error_code);
23282 } else {23304 } else {
23283 // Error must be in destination set or zero.23305 // Error must be in destination set or zero.
23284 const has_value = try block.addTyOp(.error_set_has_value, dest_ty, err_code);23306 const has_value = try block.addTyOp(.error_set_has_value, dest_err_ty, err_int_inst);
23285 const ok = try block.addBinOp(.bool_or, has_value, is_zero);23307 const ok = try block.addBinOp(.bool_or, has_value, is_zero);
23286 try sema.addSafetyCheck(block, src, ok, .invalid_error_code);23308 try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
23287 }23309 }
23288 } else {23310 } else {
23289 const err_int_inst = try block.addBitCast(err_int_ty, operand);23311 const ok = try block.addTyOp(.error_set_has_value, dest_err_ty, err_int_inst);
23290 const ok = try block.addTyOp(.error_set_has_value, dest_ty, err_int_inst);
23291 try sema.addSafetyCheck(block, src, ok, .invalid_error_code);23312 try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
23292 }23313 }
23293 }23314 }
23294 return block.addBitCast(base_dest_ty, operand);23315
23316 if (operand_tag == .error_set and dest_tag == .error_union) {
23317 const err_val = try block.addBitCast(dest_err_ty, operand);
23318 return block.addTyOp(.wrap_errunion_err, dest_ty, err_val);
23319 } else {
23320 return block.addBitCast(dest_ty, operand);
23321 }
23295}23322}
2329623323
23297fn zirPtrCastFull(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {23324fn zirPtrCastFull(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
test/behavior/error.zig+18-3
...@@ -1060,9 +1060,24 @@ test "errorCast to adhoc inferred error set" {...@@ -1060,9 +1060,24 @@ test "errorCast to adhoc inferred error set" {
1060 try std.testing.expect((try S.baz()) == 1234);1060 try std.testing.expect((try S.baz()) == 1234);
1061}1061}
10621062
1063test "errorCast from error sets to error unions" {1063test "@errorCast from error set to error union" {
1064 const err_union: Set1!void = @errorCast(error.A);1064 const S = struct {
1065 try expectError(error.A, err_union);1065 fn doTheTest(set: error{ A, B }) error{A}!i32 {
1066 return @errorCast(set);
1067 }
1068 };
1069 try expectError(error.A, S.doTheTest(error.A));
1070 try expectError(error.A, comptime S.doTheTest(error.A));
1071}
1072
1073test "@errorCast from error union to error union" {
1074 const S = struct {
1075 fn doTheTest(set: error{ A, B }!i32) error{A}!i32 {
1076 return @errorCast(set);
1077 }
1078 };
1079 try expectError(error.A, S.doTheTest(error.A));
1080 try expectError(error.A, comptime S.doTheTest(error.A));
1066}1081}
10671082
1068test "result location initialization of error union with OPV payload" {1083test "result location initialization of error union with OPV payload" {