const std = @import("std");
const builtin = @import("builtin");
const testing = std.testing;
const assert = std.debug.assert;
const expect = testing.expect;
const expectEqual = testing.expectEqual;

test "params" {
    try expect(testParamsAdd(22, 11) == 33);
}
fn testParamsAdd(a: i32, b: i32) i32 {
    return a + b;
}

test "local variables" {
    testLocVars(2);
}
fn testLocVars(b: i32) void {
    const a: i32 = 1;
    if (a + b != 3) unreachable;
}

test "mutable local variables" {
    var zero: i32 = 0;
    _ = &zero;
    try expect(zero == 0);

    var i = @as(i32, 0);
    while (i != 3) {
        i += 1;
    }
    try expect(i == 3);
}

test "separate block scopes" {
    {
        const no_conflict: i32 = 5;
        try expect(no_conflict == 5);
    }

    const c = x: {
        const no_conflict = @as(i32, 10);
        break :x no_conflict;
    };
    try expect(c == 10);
}

fn @"weird function name"() i32 {
    return 1234;
}
test "weird function name" {
    try expect(@"weird function name"() == 1234);
}

test "assign inline fn to const variable" {
    const a = inlineFn;
    a();
}

inline fn inlineFn() void {}

fn outer(y: u32) *const fn (u32) u32 {
    const Y = @TypeOf(y);
    const st = struct {
        fn get(z: u32) u32 {
            return z + @sizeOf(Y);
        }
    };
    return st.get;
}

test "return inner function which references comptime variable of outer function" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const func = outer(10);
    try expect(func(3) == 7);
}

test "discard the result of a function that returns a struct" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const S = struct {
        fn entry() void {
            _ = func();
        }

        fn func() Foo {
            return undefined;
        }

        const Foo = struct {
            a: u64,
            b: u64,
        };
    };
    S.entry();
    comptime S.entry();
}

test "inline function call that calls optional function pointer, return pointer at callsite interacts correctly with callsite return type" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        field: u32,

        fn doTheTest() !void {
            bar2 = actualFn;
            const result = try foo();
            try expect(result.field == 1234);
        }

        const Foo = struct { field: u32 };

        fn foo() !Foo {
            var res: Foo = undefined;
            res.field = bar();
            return res;
        }

        inline fn bar() u32 {
            return bar2.?();
        }

        var bar2: ?*const fn () u32 = null;

        fn actualFn() u32 {
            return 1234;
        }
    };
    try S.doTheTest();
}

test "implicit cast function unreachable return" {
    wantsFnWithVoid(fnWithUnreachable);
}

fn wantsFnWithVoid(comptime f: fn () void) void {
    _ = f;
}

fn fnWithUnreachable() noreturn {
    unreachable;
}

test "extern struct with stdcallcc fn pointer" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch == .x86) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = extern struct {
        ptr: *const fn () callconv(if (builtin.target.cpu.arch == .x86) .{ .x86_stdcall = .{} } else .c) i32,

        fn foo() callconv(if (builtin.target.cpu.arch == .x86) .{ .x86_stdcall = .{} } else .c) i32 {
            return 1234;
        }
    };

    var s: S = undefined;
    s.ptr = S.foo;
    try expect(s.ptr() == 1234);
}

const nComplexCallconv = 100;
fn fComplexCallconvRet(x: u32) callconv(blk: {
    const s: struct { n: u32 } = .{ .n = nComplexCallconv };
    break :blk switch (s.n) {
        0 => .c,
        1 => .@"inline",
        else => .auto,
    };
}) struct { x: u32 } {
    return .{ .x = x * x };
}

test "function with complex callconv and return type expressions" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try expect(fComplexCallconvRet(3).x == 9);
}

test "pass by non-copying value" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try expect(addPointCoords(Point{ .x = 1, .y = 2 }) == 3);
}

const Point = struct {
    x: i32,
    y: i32,
};

fn addPointCoords(pt: Point) i32 {
    return pt.x + pt.y;
}

test "pass by non-copying value through var arg" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try expect((try addPointCoordsVar(Point{ .x = 1, .y = 2 })) == 3);
}

fn addPointCoordsVar(pt: anytype) !i32 {
    comptime assert(@TypeOf(pt) == Point);
    return pt.x + pt.y;
}

test "pass by non-copying value as method" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    var pt = Point2{ .x = 1, .y = 2 };
    try expect(pt.addPointCoords() == 3);
}

const Point2 = struct {
    x: i32,
    y: i32,

    fn addPointCoords(self: Point2) i32 {
        return self.x + self.y;
    }
};

test "pass by non-copying value as method, which is generic" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    var pt = Point3{ .x = 1, .y = 2 };
    try expect(pt.addPointCoords(i32) == 3);
}

const Point3 = struct {
    x: i32,
    y: i32,

    fn addPointCoords(self: Point3, comptime T: type) i32 {
        _ = T;
        return self.x + self.y;
    }
};

test "pass by non-copying value as method, at comptime" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    comptime {
        var pt = Point2{ .x = 1, .y = 2 };
        try expect(pt.addPointCoords() == 3);
    }
}

test "implicit cast fn call result to optional in field result" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn entry() !void {
            const x = Foo{
                .field = optionalPtr(),
            };
            try expect(x.field.?.* == 999);
        }

        const glob: i32 = 999;

        fn optionalPtr() *const i32 {
            return &glob;
        }

        const Foo = struct {
            field: ?*const i32,
        };
    };
    try S.entry();
    try comptime S.entry();
}

test "void parameters" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;

    try voidFun(1, {}, 2, {});
}
fn voidFun(a: i32, b: void, c: i32, d: void) !void {
    _ = d;
    const v = b;
    const vv: void = if (a == 1) v else {};
    try expect(a + c == 3);
    return vv;
}

test "call function with empty string" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    acceptsString("");
}

fn acceptsString(foo: []u8) void {
    _ = foo;
}

test "function pointers" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const fns = [_]*const @TypeOf(fn1){
        &fn1,
        &fn2,
        &fn3,
        &fn4,
    };
    for (fns, 0..) |f, i| {
        try expect(f() == @as(u32, @intCast(i)) + 5);
    }
}
fn fn1() u32 {
    return 5;
}
fn fn2() u32 {
    return 6;
}
fn fn3() u32 {
    return 7;
}
fn fn4() u32 {
    return 8;
}

test "number literal as an argument" {
    try numberLiteralArg(3);
    try comptime numberLiteralArg(3);
}

fn numberLiteralArg(a: anytype) !void {
    try expect(a == 3);
}

test "function call with anon list literal" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const S = struct {
        fn doTheTest() !void {
            try consumeVec(.{ 9, 8, 7 });
        }

        fn consumeVec(vec: [3]f32) !void {
            try expect(vec[0] == 9);
            try expect(vec[1] == 8);
            try expect(vec[2] == 7);
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "function call with anon list literal - 2D" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const S = struct {
        fn doTheTest() !void {
            try consumeVec(.{ .{ 9, 8 }, .{ 7, 6 } });
        }

        fn consumeVec(vec: [2][2]f32) !void {
            try expect(vec[0][0] == 9);
            try expect(vec[0][1] == 8);
            try expect(vec[1][0] == 7);
            try expect(vec[1][1] == 6);
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "ability to give comptime types and non comptime types to same parameter" {
    const S = struct {
        fn doTheTest() !void {
            var x: i32 = 1;
            _ = &x;
            try expect(foo(x) == 10);
            try expect(foo(i32) == 20);
        }

        fn foo(arg: anytype) i32 {
            if (@typeInfo(@TypeOf(arg)) == .type and arg == i32) return 20;
            return 9 + arg;
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "function with inferred error set but returning no error" {
    const S = struct {
        fn foo() !void {}
    };

    const return_ty = @typeInfo(@TypeOf(S.foo)).@"fn".return_type.?;
    try expectEqual(0, @typeInfo(@typeInfo(return_ty).error_union.error_set).error_set.error_names.?.len);
}

test "import passed byref to function in return type" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn get() @import("std").ArrayList(i32) {
            const x: @import("std").ArrayList(i32) = .empty;
            return x;
        }
    };
    const list = S.get();
    try expect(list.items.len == 0);
}

test "implicit cast function to function ptr" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S1 = struct {
        export fn someFunctionThatReturnsAValue() c_int {
            return 123;
        }
    };
    var fnPtr1: *const fn () callconv(.c) c_int = S1.someFunctionThatReturnsAValue;
    _ = &fnPtr1;
    try expect(fnPtr1() == 123);
    const S2 = struct {
        extern fn someFunctionThatReturnsAValue() c_int;
    };
    var fnPtr2: *const fn () callconv(.c) c_int = S2.someFunctionThatReturnsAValue;
    _ = &fnPtr2;
    try expect(fnPtr2() == 123);
}

test "method call with optional and error union first param" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        x: i32 = 1234,

        fn opt(s: ?@This()) !void {
            try expect(s.?.x == 1234);
        }
        fn errUnion(s: anyerror!@This()) !void {
            try expect((try s).x == 1234);
        }
    };
    var s: S = .{};
    try s.opt();
    try s.errUnion();
}

test "method call with optional pointer first param" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        x: i32 = 1234,

        fn method(s: ?*@This()) !void {
            try expect(s.?.x == 1234);
        }
    };
    var s: S = .{};
    try s.method();
    const s_ptr = &s;
    try s_ptr.method();
}

test "using @ptrCast on function pointers" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        const A = struct { data: [4]u8 };

        fn at(arr: *const A, index: usize) *const u8 {
            return &arr.data[index];
        }

        fn run() !void {
            const a = A{ .data = "abcd".* };
            const casted_fn = @as(*const fn (*const anyopaque, usize) *const u8, @ptrCast(&at));
            const casted_impl = @as(*const anyopaque, @ptrCast(&a));
            const ptr = casted_fn(casted_impl, 2);
            try expect(ptr.* == 'c');
        }
    };

    try S.run();
    // https://github.com/ziglang/zig/issues/2626
    // try comptime S.run();
}

test "function returns function returning type" {
    const S = struct {
        fn a() fn () type {
            return (struct {
                fn b() type {
                    return u32;
                }
            }).b;
        }
    };
    try expect(S.a()() == u32);
}

test "peer type resolution of inferred error set with non-void payload" {
    const S = struct {
        fn openDataFile(mode: enum { read, write }) !u32 {
            return switch (mode) {
                .read => foo(),
                .write => bar(),
            };
        }
        fn foo() error{ a, b }!u32 {
            return 1;
        }
        fn bar() error{ c, d }!u32 {
            return 2;
        }
    };
    try expect(try S.openDataFile(.read) == 1);
}

test "lazy values passed to anytype parameter" {
    const A = struct {
        a: u32,
        fn foo(comptime a: anytype) !void {
            try expect(a[0][0] == @sizeOf(@This()));
        }
    };
    try A.foo(.{[_]usize{@sizeOf(A)}});

    const B = struct {
        fn foo(comptime a: anytype) !void {
            try expect(a.x == 0);
        }
    };
    try B.foo(.{ .x = @sizeOf(B) });

    const C = struct {};
    try expect(@as(u32, @truncate(@sizeOf(C))) == 0);

    const D = struct {};
    try expect(@sizeOf(D) << 1 == 0);
}

test "pass and return comptime-only types" {
    const S = struct {
        fn returnNull(comptime x: @TypeOf(null)) @TypeOf(null) {
            return x;
        }
        fn returnUndefined(comptime x: @TypeOf(undefined)) @TypeOf(undefined) {
            return x;
        }
    };

    try expectEqual(null, S.returnNull(null));
    try expectEqual(@as(u0, 0), S.returnUndefined(undefined));
}

test "pointer to alias behaves same as pointer to function" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn foo() u32 {
            return 11227;
        }
        const bar = foo;
    };
    var a = &S.bar;
    _ = &a;
    try std.testing.expect(S.foo() == a());
}

test "comptime parameters don't have to be marked comptime if only called at comptime" {
    const S = struct {
        fn foo(x: comptime_int, y: comptime_int) u32 {
            return x + y;
        }
    };
    comptime std.debug.assert(S.foo(5, 6) == 11);
}

test "inline function with comptime-known comptime-only return type called at runtime" {
    const S = struct {
        inline fn foo(x: *i32, y: *const i32) type {
            x.* = y.*;
            return f32;
        }
    };
    var a: i32 = 0;
    const b: i32 = 111;
    const T = S.foo(&a, &b);
    try expectEqual(111, a);
    try expectEqual(f32, T);
}

test "address of function parameter is consistent" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn paramAddrMatch(x: u8) bool {
            return &x == &x;
        }
    };
    try expect(S.paramAddrMatch(0));
    comptime assert(S.paramAddrMatch(0));
}

test "address of function parameter is consistent in other parameter type" {
    const S = struct {
        fn paramAddrMatch(comptime x: u8, y: if (&x != &x) unreachable else u8) void {
            _ = y;
        }
    };
    S.paramAddrMatch(1, 2);
}

test "address of function parameter is consistent in function return type" {
    const S = struct {
        fn paramAddrMatch(comptime x: u8) if (&x != &x) unreachable else void {}
    };
    S.paramAddrMatch(1);
}

test "function parameter self equality" {
    const S = struct {
        fn equal(x: u32) bool {
            return x == x;
        }
        fn notEqual(x: u32) bool {
            return x != x;
        }
        fn lessThan(x: u32) bool {
            return x < x;
        }
        fn lessThanOrEqual(x: u32) bool {
            return x <= x;
        }
        fn greaterThan(x: u32) bool {
            return x > x;
        }
        fn greaterThanOrEqual(x: u32) bool {
            return x >= x;
        }
    };
    try expect(S.equal(42));
    try expect(!S.notEqual(42));
    try expect(!S.lessThan(42));
    try expect(S.lessThanOrEqual(42));
    try expect(!S.greaterThan(42));
    try expect(S.greaterThanOrEqual(42));
}

test "inline call propagates comptime-known argument to generic parameter and return types" {
    const S = struct {
        inline fn f(x: bool, y: if (x) u8 else u16) if (x) bool else u32 {
            if (x) {
                comptime assert(@TypeOf(y) == u8);
                return y == 0;
            } else {
                comptime assert(@TypeOf(y) == u16);
                return y * 10;
            }
        }
        fn g(x: bool, y: if (x) u8 else u16) if (x) bool else u32 {
            if (x) {
                comptime assert(@TypeOf(y) == u8);
                return y == 0;
            } else {
                comptime assert(@TypeOf(y) == u16);
                return y * 10;
            }
        }
    };

    const a0 = S.f(true, 200); // false
    const a1 = S.f(false, 1234); // 12340

    const b0 = @call(.always_inline, S.g, .{ true, 200 }); // false
    const b1 = @call(.always_inline, S.g, .{ false, 1234 }); // 12340

    comptime assert(@TypeOf(a0) == bool);
    comptime assert(@TypeOf(b0) == bool);
    try expect(a0 == false);
    try expect(b0 == false);

    comptime assert(@TypeOf(a1) == u32);
    comptime assert(@TypeOf(b1) == u32);
    try expect(a1 == 12340);
    try expect(b1 == 12340);
}

test "inline function return type is evaluated at comptime" {
    const S = struct {
        inline fn assertComptimeAndRet(x: anytype) @TypeOf(x) {
            if (!@inComptime()) comptime unreachable;
            return x;
        }

        inline fn foo(val: anytype) assertComptimeAndRet(u16) {
            return val;
        }
    };

    const result = S.foo(123);
    comptime assert(@TypeOf(result) == u16);
    try expect(result == 123);
}

test "coerce generic function making concrete parameter generic" {
    const S = struct {
        fn foo(_: anytype, x: u32) u32 {
            comptime assert(@TypeOf(x) == u32);
            return x;
        }
    };
    const coerced: fn (anytype, anytype) u32 = S.foo;
    const result = coerced({}, 123);
    try expect(result == 123);
}

test "coerce generic function making generic parameter concrete" {
    const S = struct {
        fn foo(_: anytype, x: anytype) u32 {
            comptime assert(@TypeOf(x) == u32);
            return x;
        }
    };
    const coerced: fn (anytype, u32) u32 = S.foo;
    const result = coerced({}, 123);
    try expect(result == 123);
}

test "return undefined pointer from function, directly and by expired local" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        var global: i32 = 1;

        fn returnGlobalPointer() *i32 {
            return &global;
        }

        fn returnUndefPointer() *i32 {
            return undefined;
        }

        /// Semantically equivalent to `returnUndefPointer`.
        fn returnStackPointer() *i32 {
            var stack_allocation: i32 = 1234;
            const ptr = &stack_allocation; // defeats ast-check detection
            return ptr;
        }
    };

    const ok_ptr = S.returnGlobalPointer();
    try expect(ok_ptr.* == 1);
    const bad_ptr_1 = S.returnStackPointer();
    _ = bad_ptr_1; // dereferencing this would be illegal behavior
    const bad_ptr_2 = S.returnStackPointer();
    _ = bad_ptr_2; // dereferencing this would be illegal behavior
}

test "a function that works at comptime but not runtime" {
    const a = comptime testComptimeOnlyFn(1, 1);
    try expect(a[0] == 1);
    try expect(a[1] == 2);
}

fn testComptimeOnlyFn(x: u32, y: u32) [2]u8 {
    const xa: [x]u8 = .{1};
    const ya: [y]u8 = .{2};
    return xa ++ ya;
}
