authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-08-31 01:25:23+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-09-01 17:34:07+01:00
log9e683f0f35e21c6dce127a46c9d8c3da2d23744d
tree00d1d4ac4b3757cd45068df5272a9dcbbac6c165
parentfbac7afa0f280eb271e802046ae1f3f836a9f6b2
signaturelock-open Commit is signed but in an unrecognized format.

compiler: provide result type to operand of `try`

This is mainly useful in conjunction with Decl Literals (#9938). Resolves: #19777

5 files changed, 122 insertions(+), 3 deletions(-)

lib/std/zig/AstGen.zig+14-3
......@@ -2914,6 +2914,8 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
29142914 .validate_array_init_result_ty,
29152915 .validate_ptr_array_init,
29162916 .validate_ref_ty,
2917 .try_operand_ty,
2918 .try_ref_operand_ty,
29172919 => break :b true,
29182920
29192921 .@"defer" => unreachable,
......@@ -5887,9 +5889,18 @@ fn tryExpr(
58875889 }
58885890 const try_lc = LineColumn{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
58895891
5890 const operand_ri: ResultInfo = switch (ri.rl) {
5891 .ref, .ref_coerced_ty => .{ .rl = .ref, .ctx = .error_handling_expr },
5892 else => .{ .rl = .none, .ctx = .error_handling_expr },
5892 const operand_ri: ResultInfo = .{
5893 .rl = switch (ri.rl) {
5894 .ref => .ref,
5895 .ref_coerced_ty => |payload_ptr_ty| .{
5896 .ref_coerced_ty = try parent_gz.addUnNode(.try_ref_operand_ty, payload_ptr_ty, node),
5897 },
5898 else => if (try ri.rl.resultType(parent_gz, node)) |payload_ty| .{
5899 // `coerced_ty` is OK due to the `rvalue` call below
5900 .coerced_ty = try parent_gz.addUnNode(.try_operand_ty, payload_ty, node),
5901 } else .none,
5902 },
5903 .ctx = .error_handling_expr,
58935904 };
58945905 // This could be a pointer or value depending on the `ri` parameter.
58955906 const operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, node);
lib/std/zig/Zir.zig+16
......@@ -684,6 +684,14 @@ pub const Inst = struct {
684684 /// operator. Emit a compile error if not.
685685 /// Uses the `un_tok` union field. Token is the `&` operator. Operand is the type.
686686 validate_ref_ty,
687 /// Given a type `T`, construct the type `E!T`, where `E` is this function's error set, to be used
688 /// as the result type of a `try` operand. Generic poison is propagated.
689 /// Uses the `un_node` union field. Node is the `try` expression. Operand is the type `T`.
690 try_operand_ty,
691 /// Given a type `*T`, construct the type `*E!T`, where `E` is this function's error set, to be used
692 /// as the result type of a `try` operand whose address is taken with `&`. Generic poison is propagated.
693 /// Uses the `un_node` union field. Node is the `try` expression. Operand is the type `*T`.
694 try_ref_operand_ty,
687695
688696 // The following tags all relate to struct initialization expressions.
689697
......@@ -1254,6 +1262,8 @@ pub const Inst = struct {
12541262 .array_init_elem_type,
12551263 .array_init_elem_ptr,
12561264 .validate_ref_ty,
1265 .try_operand_ty,
1266 .try_ref_operand_ty,
12571267 .restore_err_ret_index_unconditional,
12581268 .restore_err_ret_index_fn_entry,
12591269 => false,
......@@ -1324,6 +1334,8 @@ pub const Inst = struct {
13241334 .validate_array_init_result_ty,
13251335 .validate_ptr_array_init,
13261336 .validate_ref_ty,
1337 .try_operand_ty,
1338 .try_ref_operand_ty,
13271339 => true,
13281340
13291341 .param,
......@@ -1698,6 +1710,8 @@ pub const Inst = struct {
16981710 .opt_eu_base_ptr_init = .un_node,
16991711 .coerce_ptr_elem_ty = .pl_node,
17001712 .validate_ref_ty = .un_tok,
1713 .try_operand_ty = .un_node,
1714 .try_ref_operand_ty = .un_node,
17011715
17021716 .int_from_ptr = .un_node,
17031717 .compile_error = .un_node,
......@@ -3834,6 +3848,8 @@ fn findDeclsInner(
38343848 .opt_eu_base_ptr_init,
38353849 .coerce_ptr_elem_ty,
38363850 .validate_ref_ty,
3851 .try_operand_ty,
3852 .try_ref_operand_ty,
38373853 .struct_init_empty,
38383854 .struct_init_empty_result,
38393855 .struct_init_empty_ref_result,
src/Sema.zig+71
......@@ -1177,6 +1177,8 @@ fn analyzeBodyInner(
11771177 .validate_array_init_ref_ty => try sema.zirValidateArrayInitRefTy(block, inst),
11781178 .opt_eu_base_ptr_init => try sema.zirOptEuBasePtrInit(block, inst),
11791179 .coerce_ptr_elem_ty => try sema.zirCoercePtrElemTy(block, inst),
1180 .try_operand_ty => try sema.zirTryOperandTy(block, inst, false),
1181 .try_ref_operand_ty => try sema.zirTryOperandTy(block, inst, true),
11801182
11811183 .clz => try sema.zirBitCount(block, inst, .clz, Value.clz),
11821184 .ctz => try sema.zirBitCount(block, inst, .ctz, Value.ctz),
......@@ -2024,6 +2026,22 @@ fn genericPoisonReason(sema: *Sema, block: *Block, ref: Zir.Inst.Ref) GenericPoi
20242026 const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
20252027 cur = un_node.operand;
20262028 },
2029 .try_operand_ty => {
2030 // Either the input type was itself poison, or it was a slice, which we cannot translate
2031 // to an overall result type.
2032 const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
2033 const operand_ref = sema.resolveInst(un_node.operand) catch |err| switch (err) {
2034 error.GenericPoison => unreachable, // this is a type, not a value
2035 };
2036 if (operand_ref == .generic_poison_type) {
2037 // The input was poison -- keep looking.
2038 cur = un_node.operand;
2039 continue;
2040 }
2041 // We got a poison because the result type was a slice. This is a tricky case -- let's just
2042 // not bother explaining it to the user for now...
2043 return .unknown;
2044 },
20272045 .struct_init_field_type => {
20282046 const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
20292047 const extra = sema.code.extraData(Zir.Inst.FieldType, pl_node.payload_index).data;
......@@ -4423,6 +4441,59 @@ fn zirCoercePtrElemTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
44234441 }
44244442}
44254443
4444fn zirTryOperandTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref {
4445 const pt = sema.pt;
4446 const zcu = pt.zcu;
4447 const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
4448 const src = block.nodeOffset(un_node.src_node);
4449
4450 const operand_ty = sema.resolveType(block, src, un_node.operand) catch |err| switch (err) {
4451 error.GenericPoison => return .generic_poison_type,
4452 else => |e| return e,
4453 };
4454
4455 const payload_ty = if (is_ref) ty: {
4456 if (!operand_ty.isSinglePointer(zcu)) {
4457 return .generic_poison_type; // we can't get a meaningful result type here, since it will be `*E![n]T`, and we don't know `n`.
4458 }
4459 break :ty operand_ty.childType(zcu);
4460 } else operand_ty;
4461
4462 const err_set_ty = err_set: {
4463 // There are awkward cases, like `?E`. Our strategy is to repeatedly unwrap optionals
4464 // until we hit an error union or set.
4465 var cur_ty = sema.fn_ret_ty;
4466 while (true) {
4467 switch (cur_ty.zigTypeTag(zcu)) {
4468 .error_set => break :err_set cur_ty,
4469 .error_union => break :err_set cur_ty.errorUnionSet(zcu),
4470 .optional => cur_ty = cur_ty.optionalChild(zcu),
4471 else => return sema.failWithOwnedErrorMsg(block, msg: {
4472 const msg = try sema.errMsg(src, "expected '{}', found error set", .{sema.fn_ret_ty.fmt(pt)});
4473 errdefer msg.destroy(sema.gpa);
4474 const ret_ty_src: LazySrcLoc = .{
4475 .base_node_inst = sema.getOwnerFuncDeclInst(),
4476 .offset = .{ .node_offset_fn_type_ret_ty = 0 },
4477 };
4478 try sema.errNote(ret_ty_src, msg, "function cannot return an error", .{});
4479 break :msg msg;
4480 }),
4481 }
4482 }
4483 };
4484
4485 const eu_ty = try pt.errorUnionType(err_set_ty, payload_ty);
4486
4487 if (is_ref) {
4488 var ptr_info = operand_ty.ptrInfo(zcu);
4489 ptr_info.child = eu_ty.toIntern();
4490 const eu_ptr_ty = try pt.ptrTypeSema(ptr_info);
4491 return Air.internedToRef(eu_ptr_ty.toIntern());
4492 } else {
4493 return Air.internedToRef(eu_ty.toIntern());
4494 }
4495}
4496
44264497fn zirValidateRefTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
44274498 const pt = sema.pt;
44284499 const zcu = pt.zcu;
src/print_zir.zig+2
......@@ -277,6 +277,8 @@ const Writer = struct {
277277 .opt_eu_base_ptr_init,
278278 .restore_err_ret_index_unconditional,
279279 .restore_err_ret_index_fn_entry,
280 .try_operand_ty,
281 .try_ref_operand_ty,
280282 => try self.writeUnNode(stream, inst),
281283
282284 .ref,
test/behavior/try.zig+19
......@@ -67,3 +67,22 @@ test "`try`ing an if/else expression" {
6767
6868 try std.testing.expectError(error.Test, S.getError2());
6969}
70
71test "try forwards result location" {
72 if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO
73 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
74 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
75 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
76 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
77
78 const S = struct {
79 fn foo(err: bool) error{Foo}!u32 {
80 const result: error{ Foo, Bar }!u32 = if (err) error.Foo else 123;
81 const res_int: u32 = try @errorCast(result);
82 return res_int;
83 }
84 };
85
86 try expect((S.foo(false) catch return error.TestUnexpectedResult) == 123);
87 try std.testing.expectError(error.Foo, S.foo(true));
88}