const std = @import("std");
const TestContext = @import("../../src/test.zig").TestContext;

// Self-hosted has differing levels of support for various architectures. For now we pass explicit
// target parameters to each test case. At some point we will take this to the next level and have
// a set of targets that all test cases run on unless specifically overridden. For now, each test
// case applies to only the specified target.

const linux_x64 = std.zig.CrossTarget{
    .cpu_arch = .x86_64,
    .os_tag = .linux,
};

pub fn addCases(ctx: *TestContext) !void {
    try @import("cbe.zig").addCases(ctx);
    try @import("arm.zig").addCases(ctx);
    try @import("aarch64.zig").addCases(ctx);
    try @import("llvm.zig").addCases(ctx);
    try @import("wasm.zig").addCases(ctx);
    try @import("darwin.zig").addCases(ctx);
    try @import("riscv64.zig").addCases(ctx);

    {
        var case = ctx.exe("hello world with updates", linux_x64);

        case.addError("", &[_][]const u8{
            ":85:9: error: struct 'test_case.test_case' has no member named 'main'",
        });

        // Incorrect return type
        case.addError(
            \\pub export fn _start() noreturn {
            \\}
        , &[_][]const u8{":2:1: error: expected noreturn, found void"});

        // Regular old hello world
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    print();
            \\
            \\    exit();
            \\}
            \\
            \\fn print() void {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (1),
            \\          [arg1] "{rdi}" (1),
            \\          [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
            \\          [arg3] "{rdx}" (14)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    return;
            \\}
            \\
            \\fn exit() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (0)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "Hello, World!\n",
        );

        // Convert to pub fn main
        case.addCompareOutput(
            \\pub fn main() void {
            \\    print();
            \\}
            \\
            \\fn print() void {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (1),
            \\          [arg1] "{rdi}" (1),
            \\          [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
            \\          [arg3] "{rdx}" (14)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    return;
            \\}
        ,
            "Hello, World!\n",
        );

        // Now change the message only
        case.addCompareOutput(
            \\pub fn main() void {
            \\    print();
            \\}
            \\
            \\fn print() void {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (1),
            \\          [arg1] "{rdi}" (1),
            \\          [arg2] "{rsi}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")),
            \\          [arg3] "{rdx}" (104)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    return;
            \\}
        ,
            "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n",
        );
        // Now we print it twice.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    print();
            \\    print();
            \\}
            \\
            \\fn print() void {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (1),
            \\          [arg1] "{rdi}" (1),
            \\          [arg2] "{rsi}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")),
            \\          [arg3] "{rdx}" (104)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    return;
            \\}
        ,
            \\What is up? This is a longer message that will force the data to be relocated in virtual address space.
            \\What is up? This is a longer message that will force the data to be relocated in virtual address space.
            \\
        );
    }

    {
        var case = ctx.exe("adding numbers at comptime", linux_x64);
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (1),
            \\          [arg1] "{rdi}" (1),
            \\          [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
            \\          [arg3] "{rdx}" (10 + 4)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (@as(usize, 230) + @as(usize, 1)),
            \\          [arg1] "{rdi}" (0)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "Hello, World!\n",
        );
    }

    {
        var case = ctx.exe("adding numbers at runtime and comptime", linux_x64);
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    add(3, 4);
            \\
            \\    exit();
            \\}
            \\
            \\fn add(a: u32, b: u32) void {
            \\    if (a + b != 7) unreachable;
            \\}
            \\
            \\fn exit() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (0)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
        // comptime function call
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    exit();
            \\}
            \\
            \\fn add(a: u32, b: u32) u32 {
            \\    return a + b;
            \\}
            \\
            \\const x = add(3, 4);
            \\
            \\fn exit() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (x - 7)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
        // Inline function call
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    var x: usize = 3;
            \\    const y = add(1, 2, x);
            \\    exit(y - 6);
            \\}
            \\
            \\fn add(a: usize, b: usize, c: usize) callconv(.Inline) usize {
            \\    return a + b + c;
            \\}
            \\
            \\fn exit(code: usize) noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (code)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("subtracting numbers at runtime", linux_x64);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    sub(7, 4);
            \\}
            \\
            \\fn sub(a: u32, b: u32) void {
            \\    if (a - b != 3) unreachable;
            \\}
        ,
            "",
        );
    }
    {
        var case = ctx.exe("@TypeOf", linux_x64);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    var x: usize = 0;
            \\    const z = @TypeOf(x, @as(u128, 5));
            \\    assert(z == u128);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );
        case.addCompareOutput(
            \\pub fn main() void {
            \\    const z = @TypeOf(true);
            \\    assert(z == bool);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );
        case.addError(
            \\pub fn main() void {
            \\    const z = @TypeOf(true, 1);
            \\}
        , &[_][]const u8{":2:15: error: incompatible types: 'bool' and 'comptime_int'"});
    }

    {
        var case = ctx.exe("multiplying numbers at runtime and comptime", linux_x64);
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    mul(3, 4);
            \\
            \\    exit();
            \\}
            \\
            \\fn mul(a: u32, b: u32) void {
            \\    if (a * b != 12) unreachable;
            \\}
            \\
            \\fn exit() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (0)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
        // comptime function call
        case.addCompareOutput(
            \\pub fn _start() noreturn {
            \\    exit();
            \\}
            \\
            \\fn mul(a: u32, b: u32) u32 {
            \\    return a * b;
            \\}
            \\
            \\const x = mul(3, 4);
            \\
            \\fn exit() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (x - 12)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
        // Inline function call
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    var x: usize = 5;
            \\    const y = mul(2, 3, x);
            \\    exit(y - 30);
            \\}
            \\
            \\fn mul(a: usize, b: usize, c: usize) callconv(.Inline) usize {
            \\    return a * b * c;
            \\}
            \\
            \\fn exit(code: usize) noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (code)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("assert function", linux_x64);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    add(3, 4);
            \\}
            \\
            \\fn add(a: u32, b: u32) void {
            \\    assert(a + b == 7);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
            \\
            \\fn exit() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (0)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );

        // Tests copying a register. For the `c = a + b`, it has to
        // preserve both a and b, because they are both used later.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    add(3, 4);
            \\}
            \\
            \\fn add(a: u32, b: u32) void {
            \\    const c = a + b; // 7
            \\    const d = a + c; // 10
            \\    const e = d + b; // 14
            \\    assert(e == 14);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // More stress on the liveness detection.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    add(3, 4);
            \\}
            \\
            \\fn add(a: u32, b: u32) void {
            \\    const c = a + b; // 7
            \\    const d = a + c; // 10
            \\    const e = d + b; // 14
            \\    const f = d + e; // 24
            \\    const g = e + f; // 38
            \\    const h = f + g; // 62
            \\    const i = g + h; // 100
            \\    assert(i == 100);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Requires a second move. The register allocator should figure out to re-use rax.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    add(3, 4);
            \\}
            \\
            \\fn add(a: u32, b: u32) void {
            \\    const c = a + b; // 7
            \\    const d = a + c; // 10
            \\    const e = d + b; // 14
            \\    const f = d + e; // 24
            \\    const g = e + f; // 38
            \\    const h = f + g; // 62
            \\    const i = g + h; // 100
            \\    const j = i + d; // 110
            \\    assert(j == 110);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Now we test integer return values.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(add(3, 4) == 7);
            \\    assert(add(20, 10) == 30);
            \\}
            \\
            \\fn add(a: u32, b: u32) u32 {
            \\    return a + b;
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Local mutable variables.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(add(3, 4) == 7);
            \\    assert(add(20, 10) == 30);
            \\}
            \\
            \\fn add(a: u32, b: u32) u32 {
            \\    var x: u32 = undefined;
            \\    x = 0;
            \\    x += a;
            \\    x += b;
            \\    return x;
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Optionals
        case.addCompareOutput(
            \\pub fn main() void {
            \\    const a: u32 = 2;
            \\    const b: ?u32 = a;
            \\    const c = b.?;
            \\    if (c != 2) unreachable;
            \\}
        ,
            "",
        );

        // While loops
        case.addCompareOutput(
            \\pub fn main() void {
            \\    var i: u32 = 0;
            \\    while (i < 4) : (i += 1) print();
            \\    assert(i == 4);
            \\}
            \\
            \\fn print() void {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (1),
            \\          [arg1] "{rdi}" (1),
            \\          [arg2] "{rsi}" (@ptrToInt("hello\n")),
            \\          [arg3] "{rdx}" (6)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    return;
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "hello\nhello\nhello\nhello\n",
        );

        // inline while requires the condition to be comptime known.
        case.addError(
            \\pub fn main() void {
            \\    var i: u32 = 0;
            \\    inline while (i < 4) : (i += 1) print();
            \\    assert(i == 4);
            \\}
            \\
            \\fn print() void {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (1),
            \\          [arg1] "{rdi}" (1),
            \\          [arg2] "{rsi}" (@ptrToInt("hello\n")),
            \\          [arg3] "{rdx}" (6)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    return;
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        , &[_][]const u8{":3:21: error: unable to resolve comptime value"});

        // Labeled blocks (no conditional branch)
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(add(3, 4) == 20);
            \\}
            \\
            \\fn add(a: u32, b: u32) u32 {
            \\    const x: u32 = blk: {
            \\        const c = a + b; // 7
            \\        const d = a + c; // 10
            \\        const e = d + b; // 14
            \\        break :blk e;
            \\    };
            \\    const y = x + a; // 17
            \\    const z = y + a; // 20
            \\    return z;
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // This catches a possible bug in the logic for re-using dying operands.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(add(3, 4) == 116);
            \\}
            \\
            \\fn add(a: u32, b: u32) u32 {
            \\    const x: u32 = blk: {
            \\        const c = a + b; // 7
            \\        const d = a + c; // 10
            \\        const e = d + b; // 14
            \\        const f = d + e; // 24
            \\        const g = e + f; // 38
            \\        const h = f + g; // 62
            \\        const i = g + h; // 100
            \\        const j = i + d; // 110
            \\        break :blk j;
            \\    };
            \\    const y = x + a; // 113
            \\    const z = y + a; // 116
            \\    return z;
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Spilling registers to the stack.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(add(3, 4) == 1221);
            \\    assert(mul(3, 4) == 21609);
            \\}
            \\
            \\fn add(a: u32, b: u32) u32 {
            \\    const x: u32 = blk: {
            \\        const c = a + b; // 7
            \\        const d = a + c; // 10
            \\        const e = d + b; // 14
            \\        const f = d + e; // 24
            \\        const g = e + f; // 38
            \\        const h = f + g; // 62
            \\        const i = g + h; // 100
            \\        const j = i + d; // 110
            \\        const k = i + j; // 210
            \\        const l = j + k; // 320
            \\        const m = l + c; // 327
            \\        const n = m + d; // 337
            \\        const o = n + e; // 351
            \\        const p = o + f; // 375
            \\        const q = p + g; // 413
            \\        const r = q + h; // 475
            \\        const s = r + i; // 575
            \\        const t = s + j; // 685
            \\        const u = t + k; // 895
            \\        const v = u + l; // 1215
            \\        break :blk v;
            \\    };
            \\    const y = x + a; // 1218
            \\    const z = y + a; // 1221
            \\    return z;
            \\}
            \\
            \\fn mul(a: u32, b: u32) u32 {
            \\    const x: u32 = blk: {
            \\        const c = a * a * a * a; // 81
            \\        const d = a * a * a * b; // 108
            \\        const e = a * a * b * a; // 108
            \\        const f = a * a * b * b; // 144
            \\        const g = a * b * a * a; // 108
            \\        const h = a * b * a * b; // 144
            \\        const i = a * b * b * a; // 144
            \\        const j = a * b * b * b; // 192
            \\        const k = b * a * a * a; // 108
            \\        const l = b * a * a * b; // 144
            \\        const m = b * a * b * a; // 144
            \\        const n = b * a * b * b; // 192
            \\        const o = b * b * a * a; // 144
            \\        const p = b * b * a * b; // 192
            \\        const q = b * b * b * a; // 192
            \\        const r = b * b * b * b; // 256
            \\        const s = c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r; // 2401
            \\        break :blk s;
            \\    };
            \\    const y = x * a; // 7203
            \\    const z = y * a; // 21609
            \\    return z;
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Reusing the registers of dead operands playing nicely with conditional branching.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(add(3, 4) == 791);
            \\    assert(add(4, 3) == 79);
            \\}
            \\
            \\fn add(a: u32, b: u32) u32 {
            \\    const x: u32 = if (a < b) blk: {
            \\        const c = a + b; // 7
            \\        const d = a + c; // 10
            \\        const e = d + b; // 14
            \\        const f = d + e; // 24
            \\        const g = e + f; // 38
            \\        const h = f + g; // 62
            \\        const i = g + h; // 100
            \\        const j = i + d; // 110
            \\        const k = i + j; // 210
            \\        const l = k + c; // 217
            \\        const m = l + d; // 227
            \\        const n = m + e; // 241
            \\        const o = n + f; // 265
            \\        const p = o + g; // 303
            \\        const q = p + h; // 365
            \\        const r = q + i; // 465
            \\        const s = r + j; // 575
            \\        const t = s + k; // 785
            \\        break :blk t;
            \\    } else blk: {
            \\        const t = b + b + a; // 10
            \\        const c = a + t; // 14
            \\        const d = c + t; // 24
            \\        const e = d + t; // 34
            \\        const f = e + t; // 44
            \\        const g = f + t; // 54
            \\        const h = c + g; // 68
            \\        break :blk h + b; // 71
            \\    };
            \\    const y = x + a; // 788, 75
            \\    const z = y + a; // 791, 79
            \\    return z;
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Character literals and multiline strings.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    const ignore =
            \\        \\ cool thx
            \\        \\
            \\    ;
            \\    add('ぁ', '\x03');
            \\}
            \\
            \\fn add(a: u32, b: u32) void {
            \\    assert(a + b == 12356);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Global const.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    add(aa, bb);
            \\}
            \\
            \\const aa = 'ぁ';
            \\const bb = '\x03';
            \\
            \\fn add(a: u32, b: u32) void {
            \\    assert(a + b == 12356);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Array access.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert("hello"[0] == 'h');
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Array access to a global array.
        case.addCompareOutput(
            \\const hello = "hello".*;
            \\pub fn main() void {
            \\    assert(hello[1] == 'e');
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // 64bit set stack
        case.addCompareOutput(
            \\pub fn main() void {
            \\    var i: u64 = 0xFFEEDDCCBBAA9988;
            \\    assert(i == 0xFFEEDDCCBBAA9988);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Basic for loop
        case.addCompareOutput(
            \\pub fn main() void {
            \\    for ("hello") |_| print();
            \\}
            \\
            \\fn print() void {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (1),
            \\          [arg1] "{rdi}" (1),
            \\          [arg2] "{rsi}" (@ptrToInt("hello\n")),
            \\          [arg3] "{rdx}" (6)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    return;
            \\}
        ,
            "hello\nhello\nhello\nhello\nhello\n",
        );
    }

    {
        var case = ctx.exe("basic import", linux_x64);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    @import("print.zig").print();
            \\}
        ,
            "Hello, World!\n",
        );
        try case.files.append(.{
            .src = 
            \\pub fn print() void {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (@as(usize, 1)),
            \\          [arg1] "{rdi}" (@as(usize, 1)),
            \\          [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
            \\          [arg3] "{rdx}" (@as(usize, 14))
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    return;
            \\}
            ,
            .path = "print.zig",
        });
    }
    {
        var case = ctx.exe("redundant comptime", linux_x64);
        case.addError(
            \\pub fn main() void {
            \\    var a: comptime u32 = 0;
            \\}
        ,
            &.{":2:12: error: redundant comptime keyword in already comptime scope"},
        );
        case.addError(
            \\pub fn main() void {
            \\    comptime {
            \\        var a: u32 = comptime 0;
            \\    }
            \\}
        ,
            &.{":3:22: error: redundant comptime keyword in already comptime scope"},
        );
    }
    {
        var case = ctx.exe("import private", linux_x64);
        case.addError(
            \\pub fn main() void {
            \\    @import("print.zig").print();
            \\}
        ,
            &.{
                ":2:25: error: 'print' is not marked 'pub'",
                "print.zig:2:1: note: declared here",
            },
        );
        try case.files.append(.{
            .src = 
            \\// dummy comment to make print be on line 2
            \\fn print() void {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (@as(usize, 1)),
            \\          [arg1] "{rdi}" (@as(usize, 1)),
            \\          [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
            \\          [arg3] "{rdx}" (@as(usize, 14))
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    return;
            \\}
            ,
            .path = "print.zig",
        });
    }

    ctx.compileError("function redeclaration", linux_x64,
        \\// dummy comment
        \\fn entry() void {}
        \\fn entry() void {}
        \\
        \\fn foo() void {
        \\    var foo = 1234;
        \\}
    , &[_][]const u8{
        ":3:1: error: redeclaration of 'entry'",
        ":2:1: note: other declaration here",
        ":6:9: error: local shadows declaration of 'foo'",
        ":5:1: note: declared here",
    });

    ctx.compileError("returns in try", linux_x64,
        \\pub fn main() !void {
        \\	try a();
        \\	try b();
        \\}
        \\
        \\pub fn a() !void {
        \\	defer try b();
        \\}
        \\pub fn b() !void {
        \\	defer return a();
        \\}
    , &[_][]const u8{
        ":7:8: error: try is not allowed inside defer expression",
        ":10:8: error: cannot return from defer expression",
    });

    ctx.compileError("global variable redeclaration", linux_x64,
        \\// dummy comment
        \\var foo = false;
        \\var foo = true;
    , &[_][]const u8{
        ":3:1: error: redeclaration of 'foo'",
        ":2:1: note: other declaration here",
    });

    ctx.compileError("compileError", linux_x64,
        \\export fn foo() void {
        \\  @compileError("this is an error");
        \\}
    , &[_][]const u8{":2:3: error: this is an error"});

    {
        var case = ctx.obj("variable shadowing", linux_x64);
        case.addError(
            \\pub fn main() void {
            \\    var i: u32 = 10;
            \\    var i: u32 = 10;
            \\}
        , &[_][]const u8{
            ":3:9: error: redeclaration of 'i'",
            ":2:9: note: previously declared here",
        });
        case.addError(
            \\var testing: i64 = 10;
            \\pub fn main() void {
            \\    var testing: i64 = 20;
            \\}
        , &[_][]const u8{
            ":3:9: error: local shadows declaration of 'testing'",
            ":1:1: note: declared here",
        });
        case.addError(
            \\fn a() type {
            \\    return struct {
            \\        pub fn b() void {
            \\            const c = 6;
            \\            const c = 69;
            \\        }
            \\    };
            \\}
        , &[_][]const u8{
            ":5:19: error: redeclaration of 'c'",
            ":4:19: note: previously declared here",
        });
    }

    {
        // TODO make the test harness support checking the compile log output too
        var case = ctx.obj("@compileLog", linux_x64);
        // The other compile error prevents emission of a "found compile log" statement.
        case.addError(
            \\export fn _start() noreturn {
            \\    const b = true;
            \\    var f: u32 = 1;
            \\    @compileLog(b, 20, f, x);
            \\    @compileLog(1000);
            \\    var bruh: usize = true;
            \\    unreachable;
            \\}
            \\export fn other() void {
            \\    @compileLog(1234);
            \\}
            \\fn x() void {}
        , &[_][]const u8{
            ":6:23: error: expected usize, found bool",
        });

        // Now only compile log statements remain. One per Decl.
        case.addError(
            \\export fn _start() noreturn {
            \\    const b = true;
            \\    var f: u32 = 1;
            \\    @compileLog(b, 20, f, x);
            \\    @compileLog(1000);
            \\    unreachable;
            \\}
            \\export fn other() void {
            \\    @compileLog(1234);
            \\}
            \\fn x() void {}
        , &[_][]const u8{
            ":9:5: error: found compile log statement",
            ":4:5: note: also here",
        });
    }

    {
        var case = ctx.obj("extern variable has no type", linux_x64);
        case.addError(
            \\comptime {
            \\    _ = foo;
            \\}
            \\extern var foo: i32;
        , &[_][]const u8{":2:9: error: unable to resolve comptime value"});
        case.addError(
            \\export fn entry() void {
            \\    _ = foo;
            \\}
            \\extern var foo;
        , &[_][]const u8{":4:8: error: unable to infer variable type"});
    }

    {
        var case = ctx.exe("break/continue", linux_x64);

        // Break out of loop
        case.addCompareOutput(
            \\pub fn main() void {
            \\    while (true) {
            \\        break;
            \\    }
            \\}
        ,
            "",
        );
        case.addCompareOutput(
            \\pub fn main() void {
            \\    foo: while (true) {
            \\        break :foo;
            \\    }
            \\}
        ,
            "",
        );

        // Continue in loop
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    var i: u64 = 0;
            \\    while (true) : (i+=1) {
            \\        if (i == 4) exit();
            \\        continue;
            \\    }
            \\}
            \\
            \\fn exit() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (0)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    var i: u64 = 0;
            \\    foo: while (true) : (i+=1) {
            \\        if (i == 4) exit();
            \\        continue :foo;
            \\    }
            \\}
            \\
            \\fn exit() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (0)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("unused labels", linux_x64);
        case.addError(
            \\comptime {
            \\    foo: {}
            \\}
        , &[_][]const u8{":2:5: error: unused block label"});
        case.addError(
            \\comptime {
            \\    foo: while (true) {}
            \\}
        , &[_][]const u8{":2:5: error: unused while loop label"});
        case.addError(
            \\comptime {
            \\    foo: for ("foo") |_| {}
            \\}
        , &[_][]const u8{":2:5: error: unused for loop label"});
        case.addError(
            \\comptime {
            \\    blk: {blk: {}}
            \\}
        , &[_][]const u8{
            ":2:11: error: redefinition of label 'blk'",
            ":2:5: note: previous definition is here",
        });
    }

    {
        var case = ctx.exe("bad inferred variable type", linux_x64);
        case.addError(
            \\pub fn main() void {
            \\    var x = null;
            \\}
        , &[_][]const u8{
            ":2:9: error: variable of type '@Type(.Null)' must be const or comptime",
        });
    }

    {
        var case = ctx.exe("compile error in inline fn call fixed", linux_x64);
        case.addError(
            \\pub export fn _start() noreturn {
            \\    var x: usize = 3;
            \\    const y = add(10, 2, x);
            \\    exit(y - 6);
            \\}
            \\
            \\fn add(a: usize, b: usize, c: usize) callconv(.Inline) usize {
            \\    if (a == 10) @compileError("bad");
            \\    return a + b + c;
            \\}
            \\
            \\fn exit(code: usize) noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (code)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        , &[_][]const u8{":8:18: error: bad"});

        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    var x: usize = 3;
            \\    const y = add(1, 2, x);
            \\    exit(y - 6);
            \\}
            \\
            \\fn add(a: usize, b: usize, c: usize) callconv(.Inline) usize {
            \\    if (a == 10) @compileError("bad");
            \\    return a + b + c;
            \\}
            \\
            \\fn exit(code: usize) noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (code)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
    }
    {
        var case = ctx.exe("recursive inline function", linux_x64);
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    const y = fibonacci(7);
            \\    exit(y - 21);
            \\}
            \\
            \\fn fibonacci(n: usize) callconv(.Inline) usize {
            \\    if (n <= 2) return n;
            \\    return fibonacci(n - 2) + fibonacci(n - 1);
            \\}
            \\
            \\fn exit(code: usize) noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (code)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
        // This additionally tests that the compile error reports the correct source location.
        // Without storing source locations relative to the owner decl, the compile error
        // here would be off by 2 bytes (from the "7" -> "999").
        case.addError(
            \\pub export fn _start() noreturn {
            \\    const y = fibonacci(999);
            \\    exit(y - 21);
            \\}
            \\
            \\fn fibonacci(n: usize) callconv(.Inline) usize {
            \\    if (n <= 2) return n;
            \\    return fibonacci(n - 2) + fibonacci(n - 1);
            \\}
            \\
            \\fn exit(code: usize) noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (code)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        , &[_][]const u8{":8:21: error: evaluation exceeded 1000 backwards branches"});
    }
    {
        var case = ctx.exe("orelse at comptime", linux_x64);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    const i: ?u64 = 0;
            \\    const result = i orelse 5;
            \\    assert(result == 0);
            \\}
            \\fn assert(b: bool) void {
            \\    if (!b) unreachable;
            \\}
        ,
            "",
        );
        case.addCompareOutput(
            \\pub fn main() void {
            \\    const i: ?u64 = null;
            \\    const result = i orelse 5;
            \\    assert(result == 5);
            \\}
            \\fn assert(b: bool) void {
            \\    if (!b) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("only 1 function and it gets updated", linux_x64);
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (60), // exit
            \\          [arg1] "{rdi}" (0)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    asm volatile ("syscall"
            \\        :
            \\        : [number] "{rax}" (231), // exit_group
            \\          [arg1] "{rdi}" (0)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "",
        );
    }
    {
        var case = ctx.exe("passing u0 to function", linux_x64);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    doNothing(0);
            \\}
            \\fn doNothing(arg: u0) void {}
        ,
            "",
        );
    }
    {
        var case = ctx.exe("catch at comptime", linux_x64);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    const i: anyerror!u64 = 0;
            \\    const caught = i catch 5;
            \\    assert(caught == 0);
            \\}
            \\fn assert(b: bool) void {
            \\    if (!b) unreachable;
            \\}
        ,
            "",
        );

        case.addCompareOutput(
            \\pub fn main() void {
            \\    const i: anyerror!u64 = error.B;
            \\    const caught = i catch 5;
            \\    assert(caught == 5);
            \\}
            \\fn assert(b: bool) void {
            \\    if (!b) unreachable;
            \\}
        ,
            "",
        );

        case.addCompareOutput(
            \\pub fn main() void {
            \\    const a: anyerror!comptime_int = 42;
            \\    const b: *const comptime_int = &(a catch unreachable);
            \\    assert(b.* == 42);
            \\}
            \\fn assert(b: bool) void {
            \\    if (!b) unreachable; // assertion failure
            \\}
        , "");

        case.addCompareOutput(
            \\pub fn main() void {
            \\    const a: anyerror!u32 = error.B;
            \\    _ = &(a catch |err| assert(err == error.B));
            \\}
            \\fn assert(b: bool) void {
            \\    if (!b) unreachable;
            \\}
        , "");

        case.addCompareOutput(
            \\pub fn main() void {
            \\    const a: anyerror!u32 = error.Bar;
            \\    a catch |err| assert(err == error.Bar);
            \\}
            \\fn assert(b: bool) void {
            \\    if (!b) unreachable;
            \\}
        , "");
    }
    {
        var case = ctx.exe("merge error sets", linux_x64);

        case.addCompareOutput(
            \\pub fn main() void {
            \\    const E = error{ A, B, D } || error { A, B, C };
            \\    const a = E.A;
            \\    const b = E.B;
            \\    const c = E.C;
            \\    const d = E.D;
            \\    const E2 = error { X, Y } || @TypeOf(error.Z);
            \\    const x = E2.X;
            \\    const y = E2.Y;
            \\    const z = E2.Z;
            \\    assert(anyerror || error { Z } == anyerror);
            \\}
            \\fn assert(b: bool) void {
            \\    if (!b) unreachable;
            \\}
        ,
            "",
        );
    }
    {
        var case = ctx.exe("inline assembly", linux_x64);

        case.addError(
            \\pub fn main() void {
            \\    const number = 1234;
            \\    const x = asm volatile ("syscall"
            \\        : [o] "{rax}" (-> number)
            \\        : [number] "{rax}" (231),
            \\          [arg1] "{rdi}" (code)
            \\        : "rcx", "r11", "memory"
            \\    );
            \\}
        , &[_][]const u8{":4:27: error: expected type, found comptime_int"});
    }
}
