| author | |
| committer | |
| log | 3924f173af51c50ac18be2166fe115555241778e |
| tree | ee3fa65cc85dd6022af6533068dc3fb4f46461ab |
| parent | c225b780e3944014300555c3b45eaccd7bc7c8ae |
This commit effectively reverts 9e683f0, and hence un-accepts #19777.
While nice in theory, this proposal turned out to have a few problems.
Firstly, supplying a result type implicitly coerces the operand to this
type -- that's the main point of result types! But for `try`, this is
actually a bad idea; we want a redundant `try` to be a compile error,
not to silently coerce the non-error value to an error union. In
practice, this didn't always happen, because the implementation was
buggy anyway; but when it did, it was really quite silly. For instance,
`try try ... try .{ ... }` was an accepted expression, with the inner
initializer being initially coerced to `E!E!...E!T`.
Secondly, the result type inference here didn't play nicely with
`return`. If you write `return try`, the operand would actually receive
a result type of `E!E!T`, since the `return` gave a result type of `E!T`
and the `try` wrapped it in *another* error union. More generally, the
problem here is that `try` doesn't know when it should or shouldn't
nest error unions. This occasionally broke code which looked like it
should work.
So, this commit prevents `try` from propagating result types through to
its operand. A key motivation for the original proposal here was decl
literals; so, as a special case, `try .foo(...)` is still an allowed
syntax form, caught by AstGen and specially lowered. This does open the
doors to allowing other special cases for decl literals in future, such
as `.foo(...) catch ...`, but those proposals are for another time.
Resolves: #21991
Resolves: #226336 files changed, 105 insertions(+), 72 deletions(-)
lib/std/zig/AstGen.zig+32-19| ... | ... | @@ -851,7 +851,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE |
| 851 | 851 | .async_call_comma, |
| 852 | 852 | => { |
| 853 | 853 | var buf: [1]Ast.Node.Index = undefined; |
| 854 | return callExpr(gz, scope, ri, node, tree.fullCall(&buf, node).?); | |
| 854 | return callExpr(gz, scope, ri, .none, node, tree.fullCall(&buf, node).?); | |
| 855 | 855 | }, |
| 856 | 856 | |
| 857 | 857 | .unreachable_literal => { |
| ... | ... | @@ -3009,8 +3009,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As |
| 3009 | 3009 | .validate_ptr_array_init, |
| 3010 | 3010 | .validate_ref_ty, |
| 3011 | 3011 | .validate_const, |
| 3012 | .try_operand_ty, | |
| 3013 | .try_ref_operand_ty, | |
| 3014 | 3012 | => break :b true, |
| 3015 | 3013 | |
| 3016 | 3014 | .@"defer" => unreachable, |
| ... | ... | @@ -6158,20 +6156,24 @@ fn tryExpr( |
| 6158 | 6156 | const try_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column }; |
| 6159 | 6157 | |
| 6160 | 6158 | const operand_rl: ResultInfo.Loc, const block_tag: Zir.Inst.Tag = switch (ri.rl) { |
| 6161 | .ref => .{ .ref, .try_ptr }, | |
| 6162 | .ref_coerced_ty => |payload_ptr_ty| .{ | |
| 6163 | .{ .ref_coerced_ty = try parent_gz.addUnNode(.try_ref_operand_ty, payload_ptr_ty, node) }, | |
| 6164 | .try_ptr, | |
| 6165 | }, | |
| 6166 | else => if (try ri.rl.resultType(parent_gz, node)) |payload_ty| .{ | |
| 6167 | // `coerced_ty` is OK due to the `rvalue` call below | |
| 6168 | .{ .coerced_ty = try parent_gz.addUnNode(.try_operand_ty, payload_ty, node) }, | |
| 6169 | .@"try", | |
| 6170 | } else .{ .none, .@"try" }, | |
| 6159 | .ref, .ref_coerced_ty => .{ .ref, .try_ptr }, | |
| 6160 | else => .{ .none, .@"try" }, | |
| 6171 | 6161 | }; |
| 6172 | 6162 | const operand_ri: ResultInfo = .{ .rl = operand_rl, .ctx = .error_handling_expr }; |
| 6173 | // This could be a pointer or value depending on the `ri` parameter. | |
| 6174 | const operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, node); | |
| 6163 | const operand = operand: { | |
| 6164 | // As a special case, we need to detect this form: | |
| 6165 | // `try .foo(...)` | |
| 6166 | // This is a decl literal form, even though we don't propagate a result type through `try`. | |
| 6167 | var buf: [1]Ast.Node.Index = undefined; | |
| 6168 | if (astgen.tree.fullCall(&buf, operand_node)) |full_call| { | |
| 6169 | const res_ty: Zir.Inst.Ref = try ri.rl.resultType(parent_gz, operand_node) orelse .none; | |
| 6170 | break :operand try callExpr(parent_gz, scope, operand_ri, res_ty, operand_node, full_call); | |
| 6171 | } | |
| 6172 | ||
| 6173 | // This could be a pointer or value depending on the `ri` parameter. | |
| 6174 | break :operand try reachableExpr(parent_gz, scope, operand_ri, operand_node, node); | |
| 6175 | }; | |
| 6176 | ||
| 6175 | 6177 | const try_inst = try parent_gz.makeBlockInst(block_tag, node); |
| 6176 | 6178 | try parent_gz.instructions.append(astgen.gpa, try_inst); |
| 6177 | 6179 | |
| ... | ... | @@ -10236,12 +10238,15 @@ fn callExpr( |
| 10236 | 10238 | gz: *GenZir, |
| 10237 | 10239 | scope: *Scope, |
| 10238 | 10240 | ri: ResultInfo, |
| 10241 | /// If this is not `.none` and this call is a decl literal form (`.foo(...)`), then this | |
| 10242 | /// type is used as the decl literal result type instead of the result type from `ri.rl`. | |
| 10243 | override_decl_literal_type: Zir.Inst.Ref, | |
| 10239 | 10244 | node: Ast.Node.Index, |
| 10240 | 10245 | call: Ast.full.Call, |
| 10241 | 10246 | ) InnerError!Zir.Inst.Ref { |
| 10242 | 10247 | const astgen = gz.astgen; |
| 10243 | 10248 | |
| 10244 | const callee = try calleeExpr(gz, scope, ri.rl, call.ast.fn_expr); | |
| 10249 | const callee = try calleeExpr(gz, scope, ri.rl, override_decl_literal_type, call.ast.fn_expr); | |
| 10245 | 10250 | const modifier: std.builtin.CallModifier = blk: { |
| 10246 | 10251 | if (call.async_token != null) { |
| 10247 | 10252 | break :blk .async_kw; |
| ... | ... | @@ -10367,6 +10372,9 @@ fn calleeExpr( |
| 10367 | 10372 | gz: *GenZir, |
| 10368 | 10373 | scope: *Scope, |
| 10369 | 10374 | call_rl: ResultInfo.Loc, |
| 10375 | /// If this is not `.none` and this call is a decl literal form (`.foo(...)`), then this | |
| 10376 | /// type is used as the decl literal result type instead of the result type from `call_rl`. | |
| 10377 | override_decl_literal_type: Zir.Inst.Ref, | |
| 10370 | 10378 | node: Ast.Node.Index, |
| 10371 | 10379 | ) InnerError!Callee { |
| 10372 | 10380 | const astgen = gz.astgen; |
| ... | ... | @@ -10393,7 +10401,14 @@ fn calleeExpr( |
| 10393 | 10401 | .field_name_start = str_index, |
| 10394 | 10402 | } }; |
| 10395 | 10403 | }, |
| 10396 | .enum_literal => if (try call_rl.resultType(gz, node)) |res_ty| { | |
| 10404 | .enum_literal => { | |
| 10405 | const res_ty = res_ty: { | |
| 10406 | if (override_decl_literal_type != .none) break :res_ty override_decl_literal_type; | |
| 10407 | break :res_ty try call_rl.resultType(gz, node) orelse { | |
| 10408 | // No result type; lower to a literal call of an enum literal. | |
| 10409 | return .{ .direct = try expr(gz, scope, .{ .rl = .none }, node) }; | |
| 10410 | }; | |
| 10411 | }; | |
| 10397 | 10412 | // Decl literal call syntax, e.g. |
| 10398 | 10413 | // `const foo: T = .init();` |
| 10399 | 10414 | // Look up `init` in `T`, but don't try and coerce it. |
| ... | ... | @@ -10403,8 +10418,6 @@ fn calleeExpr( |
| 10403 | 10418 | .field_name_start = str_index, |
| 10404 | 10419 | }); |
| 10405 | 10420 | return .{ .direct = callee }; |
| 10406 | } else { | |
| 10407 | return .{ .direct = try expr(gz, scope, .{ .rl = .none }, node) }; | |
| 10408 | 10421 | }, |
| 10409 | 10422 | else => return .{ .direct = try expr(gz, scope, .{ .rl = .none }, node) }, |
| 10410 | 10423 | } |
lib/std/zig/Zir.zig-16| ... | ... | @@ -721,14 +721,6 @@ pub const Inst = struct { |
| 721 | 721 | /// Result is always void. |
| 722 | 722 | /// Uses the `un_node` union field. Node is the initializer. Operand is the initializer value. |
| 723 | 723 | validate_const, |
| 724 | /// Given a type `T`, construct the type `E!T`, where `E` is this function's error set, to be used | |
| 725 | /// as the result type of a `try` operand. Generic poison is propagated. | |
| 726 | /// Uses the `un_node` union field. Node is the `try` expression. Operand is the type `T`. | |
| 727 | try_operand_ty, | |
| 728 | /// Given a type `*T`, construct the type `*E!T`, where `E` is this function's error set, to be used | |
| 729 | /// as the result type of a `try` operand whose address is taken with `&`. Generic poison is propagated. | |
| 730 | /// Uses the `un_node` union field. Node is the `try` expression. Operand is the type `*T`. | |
| 731 | try_ref_operand_ty, | |
| 732 | 724 | |
| 733 | 725 | // The following tags all relate to struct initialization expressions. |
| 734 | 726 | |
| ... | ... | @@ -1304,8 +1296,6 @@ pub const Inst = struct { |
| 1304 | 1296 | .array_init_elem_ptr, |
| 1305 | 1297 | .validate_ref_ty, |
| 1306 | 1298 | .validate_const, |
| 1307 | .try_operand_ty, | |
| 1308 | .try_ref_operand_ty, | |
| 1309 | 1299 | .restore_err_ret_index_unconditional, |
| 1310 | 1300 | .restore_err_ret_index_fn_entry, |
| 1311 | 1301 | => false, |
| ... | ... | @@ -1365,8 +1355,6 @@ pub const Inst = struct { |
| 1365 | 1355 | .validate_ptr_array_init, |
| 1366 | 1356 | .validate_ref_ty, |
| 1367 | 1357 | .validate_const, |
| 1368 | .try_operand_ty, | |
| 1369 | .try_ref_operand_ty, | |
| 1370 | 1358 | => true, |
| 1371 | 1359 | |
| 1372 | 1360 | .param, |
| ... | ... | @@ -1749,8 +1737,6 @@ pub const Inst = struct { |
| 1749 | 1737 | .coerce_ptr_elem_ty = .pl_node, |
| 1750 | 1738 | .validate_ref_ty = .un_tok, |
| 1751 | 1739 | .validate_const = .un_node, |
| 1752 | .try_operand_ty = .un_node, | |
| 1753 | .try_ref_operand_ty = .un_node, | |
| 1754 | 1740 | |
| 1755 | 1741 | .int_from_ptr = .un_node, |
| 1756 | 1742 | .compile_error = .un_node, |
| ... | ... | @@ -4196,8 +4182,6 @@ fn findTrackableInner( |
| 4196 | 4182 | .coerce_ptr_elem_ty, |
| 4197 | 4183 | .validate_ref_ty, |
| 4198 | 4184 | .validate_const, |
| 4199 | .try_operand_ty, | |
| 4200 | .try_ref_operand_ty, | |
| 4201 | 4185 | .struct_init_empty, |
| 4202 | 4186 | .struct_init_empty_result, |
| 4203 | 4187 | .struct_init_empty_ref_result, |
src/Sema.zig-16| ... | ... | @@ -1257,8 +1257,6 @@ fn analyzeBodyInner( |
| 1257 | 1257 | .validate_array_init_ref_ty => try sema.zirValidateArrayInitRefTy(block, inst), |
| 1258 | 1258 | .opt_eu_base_ptr_init => try sema.zirOptEuBasePtrInit(block, inst), |
| 1259 | 1259 | .coerce_ptr_elem_ty => try sema.zirCoercePtrElemTy(block, inst), |
| 1260 | .try_operand_ty => try sema.zirTryOperandTy(block, inst, false), | |
| 1261 | .try_ref_operand_ty => try sema.zirTryOperandTy(block, inst, true), | |
| 1262 | 1260 | |
| 1263 | 1261 | .clz => try sema.zirBitCount(block, inst, .clz, Value.clz), |
| 1264 | 1262 | .ctz => try sema.zirBitCount(block, inst, .ctz, Value.ctz), |
| ... | ... | @@ -2085,20 +2083,6 @@ fn genericPoisonReason(sema: *Sema, block: *Block, ref: Zir.Inst.Ref) GenericPoi |
| 2085 | 2083 | const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; |
| 2086 | 2084 | cur = un_node.operand; |
| 2087 | 2085 | }, |
| 2088 | .try_operand_ty => { | |
| 2089 | // Either the input type was itself poison, or it was a slice, which we cannot translate | |
| 2090 | // to an overall result type. | |
| 2091 | const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; | |
| 2092 | const operand_ref = try sema.resolveInst(un_node.operand); | |
| 2093 | if (operand_ref == .generic_poison_type) { | |
| 2094 | // The input was poison -- keep looking. | |
| 2095 | cur = un_node.operand; | |
| 2096 | continue; | |
| 2097 | } | |
| 2098 | // We got a poison because the result type was a slice. This is a tricky case -- let's just | |
| 2099 | // not bother explaining it to the user for now... | |
| 2100 | return .unknown; | |
| 2101 | }, | |
| 2102 | 2086 | .struct_init_field_type => { |
| 2103 | 2087 | const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; |
| 2104 | 2088 | const extra = sema.code.extraData(Zir.Inst.FieldType, pl_node.payload_index).data; |
src/print_zir.zig-2| ... | ... | @@ -278,8 +278,6 @@ const Writer = struct { |
| 278 | 278 | .opt_eu_base_ptr_init, |
| 279 | 279 | .restore_err_ret_index_unconditional, |
| 280 | 280 | .restore_err_ret_index_fn_entry, |
| 281 | .try_operand_ty, | |
| 282 | .try_ref_operand_ty, | |
| 283 | 281 | => try self.writeUnNode(stream, inst), |
| 284 | 282 | |
| 285 | 283 | .ref, |
test/behavior/try.zig+21-19| ... | ... | @@ -68,25 +68,6 @@ test "`try`ing an if/else expression" { |
| 68 | 68 | try std.testing.expectError(error.Test, S.getError2()); |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | test "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 | } | |
| 89 | ||
| 90 | 71 | test "'return try' of empty error set in function returning non-error" { |
| 91 | 72 | if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO |
| 92 | 73 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO |
| ... | ... | @@ -123,3 +104,24 @@ test "'return try' of empty error set in function returning non-error" { |
| 123 | 104 | try S.doTheTest(); |
| 124 | 105 | try comptime S.doTheTest(); |
| 125 | 106 | } |
| 107 | ||
| 108 | test "'return try' through conditional" { | |
| 109 | const S = struct { | |
| 110 | fn get(t: bool) !u32 { | |
| 111 | return try if (t) inner() else error.TestFailed; | |
| 112 | } | |
| 113 | fn inner() !u16 { | |
| 114 | return 123; | |
| 115 | } | |
| 116 | }; | |
| 117 | ||
| 118 | { | |
| 119 | const result = try S.get(true); | |
| 120 | try expect(result == 123); | |
| 121 | } | |
| 122 | ||
| 123 | { | |
| 124 | const result = try comptime S.get(true); | |
| 125 | comptime std.debug.assert(result == 123); | |
| 126 | } | |
| 127 | } |
test/cases/compile_errors/redundant_try.zig created+52| ... | ... | @@ -0,0 +1,52 @@ |
| 1 | const S = struct { x: u32 = 0 }; | |
| 2 | const T = struct { []const u8 }; | |
| 3 | ||
| 4 | fn test0() !void { | |
| 5 | const x: u8 = try 1; | |
| 6 | _ = x; | |
| 7 | } | |
| 8 | ||
| 9 | fn test1() !void { | |
| 10 | const x: S = try .{}; | |
| 11 | _ = x; | |
| 12 | } | |
| 13 | ||
| 14 | fn test2() !void { | |
| 15 | const x: S = try .{ .x = 123 }; | |
| 16 | _ = x; | |
| 17 | } | |
| 18 | ||
| 19 | fn test3() !void { | |
| 20 | const x: S = try try .{ .x = 123 }; | |
| 21 | _ = x; | |
| 22 | } | |
| 23 | ||
| 24 | fn test4() !void { | |
| 25 | const x: T = try .{"hello"}; | |
| 26 | _ = x; | |
| 27 | } | |
| 28 | ||
| 29 | fn test5() !void { | |
| 30 | const x: error{Foo}!u32 = 123; | |
| 31 | _ = try try x; | |
| 32 | } | |
| 33 | ||
| 34 | comptime { | |
| 35 | _ = &test0; | |
| 36 | _ = &test1; | |
| 37 | _ = &test2; | |
| 38 | _ = &test3; | |
| 39 | _ = &test4; | |
| 40 | _ = &test5; | |
| 41 | } | |
| 42 | ||
| 43 | // error | |
| 44 | // | |
| 45 | // :5:23: error: expected error union type, found 'comptime_int' | |
| 46 | // :10:23: error: expected error union type, found '@TypeOf(.{})' | |
| 47 | // :15:23: error: expected error union type, found 'tmp.test2__struct_493' | |
| 48 | // :15:23: note: struct declared here | |
| 49 | // :20:27: error: expected error union type, found 'tmp.test3__struct_495' | |
| 50 | // :20:27: note: struct declared here | |
| 51 | // :25:23: error: expected error union type, found 'struct { comptime *const [5:0]u8 = "hello" }' | |
| 52 | // :31:13: error: expected error union type, found 'u32' |