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_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) 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_arm) return error.SkipZigTest; // TODO
    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);
    }
}

test "try ptr propagation const" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;

    const S = struct {
        fn foo0() !u32 {
            return 0;
        }

        fn foo1() error{Bad}!u32 {
            return 1;
        }

        fn foo2() anyerror!u32 {
            return 2;
        }

        fn doTheTest() !void {
            const res0: *const u32 = &(try foo0());
            const res1: *const u32 = &(try foo1());
            const res2: *const u32 = &(try foo2());
            try expect(res0.* == 0);
            try expect(res1.* == 1);
            try expect(res2.* == 2);
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "try ptr propagation mutate" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;

    const S = struct {
        fn foo0() !u32 {
            return 0;
        }

        fn foo1() error{Bad}!u32 {
            return 1;
        }

        fn foo2() anyerror!u32 {
            return 2;
        }

        fn doTheTest() !void {
            var f0 = foo0();
            var f1 = foo1();
            var f2 = foo2();

            const res0: *u32 = &(try f0);
            const res1: *u32 = &(try f1);
            const res2: *u32 = &(try f2);

            res0.* += 1;
            res1.* += 1;
            res2.* += 1;

            try expect(f0 catch unreachable == 1);
            try expect(f1 catch unreachable == 2);
            try expect(f2 catch unreachable == 3);

            try expect(res0.* == 1);
            try expect(res1.* == 2);
            try expect(res2.* == 3);
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}
