mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
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: #22633
128 lines
3.4 KiB
Zig
128 lines
3.4 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const expect = std.testing.expect;
|
|
|
|
test "try on error union" {
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
|
|
try tryOnErrorUnionImpl();
|
|
try comptime tryOnErrorUnionImpl();
|
|
}
|
|
|
|
fn tryOnErrorUnionImpl() !void {
|
|
const x = if (returnsTen()) |val| val + 1 else |err| switch (err) {
|
|
error.ItBroke, error.NoMem => 1,
|
|
error.CrappedOut => @as(i32, 2),
|
|
else => unreachable,
|
|
};
|
|
try expect(x == 11);
|
|
}
|
|
|
|
fn returnsTen() anyerror!i32 {
|
|
return 10;
|
|
}
|
|
|
|
test "try without vars" {
|
|
const result1 = if (failIfTrue(true)) 1 else |_| @as(i32, 2);
|
|
try expect(result1 == 2);
|
|
|
|
const result2 = if (failIfTrue(false)) 1 else |_| @as(i32, 2);
|
|
try expect(result2 == 1);
|
|
}
|
|
|
|
fn failIfTrue(ok: bool) anyerror!void {
|
|
if (ok) {
|
|
return error.ItBroke;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
test "try then not executed with assignment" {
|
|
if (failIfTrue(true)) {
|
|
unreachable;
|
|
} else |err| {
|
|
try expect(err == error.ItBroke);
|
|
}
|
|
}
|
|
|
|
test "`try`ing an if/else expression" {
|
|
if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
|
|
|
|
const S = struct {
|
|
fn getError() !void {
|
|
return error.Test;
|
|
}
|
|
|
|
fn getError2() !void {
|
|
var a: u8 = 'c';
|
|
_ = &a;
|
|
try if (a == 'a') getError() else if (a == 'b') getError() else getError();
|
|
}
|
|
};
|
|
|
|
try std.testing.expectError(error.Test, S.getError2());
|
|
}
|
|
|
|
test "'return try' of empty error set in function returning non-error" {
|
|
if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
|
|
|
|
const S = struct {
|
|
fn succeed0() error{}!u32 {
|
|
return 123;
|
|
}
|
|
fn succeed1() !u32 {
|
|
return 456;
|
|
}
|
|
fn tryNoError0() u32 {
|
|
return try succeed0();
|
|
}
|
|
fn tryNoError1() u32 {
|
|
return try succeed1();
|
|
}
|
|
fn tryNoError2() u32 {
|
|
const e: error{}!u32 = 789;
|
|
return try e;
|
|
}
|
|
fn doTheTest() !void {
|
|
const res0 = tryNoError0();
|
|
const res1 = tryNoError1();
|
|
const res2 = tryNoError2();
|
|
try expect(res0 == 123);
|
|
try expect(res1 == 456);
|
|
try expect(res2 == 789);
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
try comptime S.doTheTest();
|
|
}
|
|
|
|
test "'return try' through conditional" {
|
|
const S = struct {
|
|
fn get(t: bool) !u32 {
|
|
return try if (t) inner() else error.TestFailed;
|
|
}
|
|
fn inner() !u16 {
|
|
return 123;
|
|
}
|
|
};
|
|
|
|
{
|
|
const result = try S.get(true);
|
|
try expect(result == 123);
|
|
}
|
|
|
|
{
|
|
const result = try comptime S.get(true);
|
|
comptime std.debug.assert(result == 123);
|
|
}
|
|
}
|