zig/test/cbe.zig
David Rubin 1b8f7e46fa
AstGen: detect duplicate field names
This logic was previously in Sema, which was unnecessary complexity, and meant the issue was not detected unless the declaration was semantically analyzed. This commit finishes the work which 941090d started.

Resolves: #17916
2024-01-20 17:23:47 +00:00

954 lines
29 KiB
Zig

const std = @import("std");
const Cases = @import("src/Cases.zig");
const nl = if (@import("builtin").os.tag == .windows) "\r\n" else "\n";
pub fn addCases(ctx: *Cases, b: *std.Build) !void {
// These tests should work with all platforms, but we're using linux_x64 for
// now for consistency. Will be expanded eventually.
const linux_x64: std.Target.Query = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
};
{
var case = ctx.exeFromCompiledC("hello world with updates", .{}, b);
// Regular old hello world
case.addCompareOutput(
\\extern fn puts(s: [*:0]const u8) c_int;
\\pub export fn main() c_int {
\\ _ = puts("hello world!");
\\ return 0;
\\}
, "hello world!" ++ nl);
// Now change the message only
case.addCompareOutput(
\\extern fn puts(s: [*:0]const u8) c_int;
\\pub export fn main() c_int {
\\ _ = puts("yo");
\\ return 0;
\\}
, "yo" ++ nl);
// Add an unused Decl
case.addCompareOutput(
\\extern fn puts(s: [*:0]const u8) c_int;
\\pub export fn main() c_int {
\\ _ = puts("yo!");
\\ return 0;
\\}
\\fn unused() void {}
, "yo!" ++ nl);
// Comptime return type and calling convention expected.
case.addError(
\\var x: i32 = 1234;
\\pub export fn main() x {
\\ return 0;
\\}
\\export fn foo() callconv(y) c_int {
\\ return 0;
\\}
\\var y: @import("std").builtin.CallingConvention = .C;
, &.{
":2:22: error: expected type 'type', found 'i32'",
":5:26: error: unable to resolve comptime value",
":5:26: note: calling convention must be comptime-known",
});
}
{
var case = ctx.exeFromCompiledC("var args", .{}, b);
case.addCompareOutput(
\\extern fn printf(format: [*:0]const u8, ...) c_int;
\\
\\pub export fn main() c_int {
\\ _ = printf("Hello, %s!\n", "world");
\\ return 0;
\\}
, "Hello, world!" ++ nl);
}
{
var case = ctx.exeFromCompiledC("errorFromInt", .{}, b);
case.addCompareOutput(
\\pub export fn main() c_int {
\\ // comptime checks
\\ const a = error.A;
\\ const b = error.B;
\\ const c = @errorFromInt(2);
\\ const d = @errorFromInt(1);
\\ if (!(c == b)) unreachable;
\\ if (!(a == d)) unreachable;
\\ // runtime checks
\\ var x = error.A;
\\ var y = error.B;
\\ var z = @errorFromInt(2);
\\ var f = @errorFromInt(1);
\\ if (!(y == z)) unreachable;
\\ if (!(x == f)) unreachable;
\\ return 0;
\\}
, "");
case.addError(
\\pub export fn main() c_int {
\\ _ = @errorFromInt(0);
\\ return 0;
\\}
, &.{":2:21: error: integer value '0' represents no error"});
case.addError(
\\pub export fn main() c_int {
\\ _ = @errorFromInt(3);
\\ return 0;
\\}
, &.{":2:21: error: integer value '3' represents no error"});
}
{
var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64, b);
// Exit with 0
case.addCompareOutput(
\\fn exitGood() noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ );
\\ unreachable;
\\}
\\
\\pub export fn main() c_int {
\\ exitGood();
\\}
, "");
// Pass a usize parameter to exit
case.addCompareOutput(
\\pub export fn main() c_int {
\\ exit(0);
\\}
\\
\\fn exit(code: usize) noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (code)
\\ );
\\ unreachable;
\\}
, "");
// Change the parameter to u8
case.addCompareOutput(
\\pub export fn main() c_int {
\\ exit(0);
\\}
\\
\\fn exit(code: u8) noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (code)
\\ );
\\ unreachable;
\\}
, "");
// Do some arithmetic at the exit callsite
case.addCompareOutput(
\\pub export fn main() c_int {
\\ exitMath(1);
\\}
\\
\\fn exitMath(a: u8) noreturn {
\\ exit(0 + a - a);
\\}
\\
\\fn exit(code: u8) noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (code)
\\ );
\\ unreachable;
\\}
\\
, "");
// Invert the arithmetic
case.addCompareOutput(
\\pub export fn main() c_int {
\\ exitMath(1);
\\}
\\
\\fn exitMath(a: u8) noreturn {
\\ exit(a + 0 - a);
\\}
\\
\\fn exit(code: u8) noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (code)
\\ );
\\ unreachable;
\\}
\\
, "");
}
{
var case = ctx.exeFromCompiledC("alloc and retptr", .{}, b);
case.addCompareOutput(
\\fn add(a: i32, b: i32) i32 {
\\ return a + b;
\\}
\\
\\fn addIndirect(a: i32, b: i32) i32 {
\\ return add(a, b);
\\}
\\
\\pub export fn main() c_int {
\\ return addIndirect(1, 2) - 3;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("inferred local const and var", .{}, b);
case.addCompareOutput(
\\fn add(a: i32, b: i32) i32 {
\\ return a + b;
\\}
\\
\\pub export fn main() c_int {
\\ const x = add(1, 2);
\\ var y = add(3, 0);
\\ y -= x;
\\ return y;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("control flow", .{}, b);
// Simple while loop
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var a: c_int = 0;
\\ while (a < 5) : (a+=1) {}
\\ return a - 5;
\\}
, "");
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var a = true;
\\ while (!a) {}
\\ return 0;
\\}
, "");
// If expression
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ var a: c_int = @as(c_int, if (cond == 0)
\\ 2
\\ else
\\ 3) + 9;
\\ return a - 11;
\\}
, "");
// If expression with breakpoint that does not get hit
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var x: i32 = 1;
\\ if (x != 1) @breakpoint();
\\ return 0;
\\}
, "");
// Switch expression
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ var a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 99...300, 12 => 3,
\\ 0 => 4,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, "");
// Switch expression missing else case.
case.addError(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ const a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 3 => 3,
\\ 4 => 4,
\\ };
\\ return a - 4;
\\}
, &.{":3:22: error: switch must handle all possibilities"});
// Switch expression, has an unreachable prong.
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ const a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 99...300, 12 => 3,
\\ 0 => 4,
\\ 13 => unreachable,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, "");
// Switch expression, has an unreachable prong and prongs write
// to result locations.
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ var a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 99...300, 12 => 3,
\\ 0 => 4,
\\ 13 => unreachable,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, "");
// Integer switch expression has duplicate case value.
case.addError(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ const a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 96, 11...13, 97 => 3,
\\ 0 => 4,
\\ 90, 12 => 100,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, &.{
":8:13: error: duplicate switch value",
":6:15: note: previous value here",
});
// Boolean switch expression has duplicate case value.
case.addError(
\\pub export fn main() c_int {
\\ var a: bool = false;
\\ const b: c_int = switch (a) {
\\ false => 1,
\\ true => 2,
\\ false => 3,
\\ };
\\ _ = b;
\\}
, &.{
":6:9: error: duplicate switch value",
});
// Sparse (no range capable) switch expression has duplicate case value.
case.addError(
\\pub export fn main() c_int {
\\ const A: type = i32;
\\ const b: c_int = switch (A) {
\\ i32 => 1,
\\ bool => 2,
\\ f64, i32 => 3,
\\ else => 4,
\\ };
\\ _ = b;
\\}
, &.{
":6:14: error: duplicate switch value",
":4:9: note: previous value here",
});
// Ranges not allowed for some kinds of switches.
case.addError(
\\pub export fn main() c_int {
\\ const A: type = i32;
\\ const b: c_int = switch (A) {
\\ i32 => 1,
\\ bool => 2,
\\ f16...f64 => 3,
\\ else => 4,
\\ };
\\ _ = b;
\\}
, &.{
":3:30: error: ranges not allowed when switching on type 'type'",
":6:12: note: range here",
});
// Switch expression has unreachable else prong.
case.addError(
\\pub export fn main() c_int {
\\ var a: u2 = 0;
\\ const b: i32 = switch (a) {
\\ 0 => 10,
\\ 1 => 20,
\\ 2 => 30,
\\ 3 => 40,
\\ else => 50,
\\ };
\\ _ = b;
\\}
, &.{
":8:14: error: unreachable else prong; all cases already handled",
});
}
//{
// var case = ctx.exeFromCompiledC("optionals", .{}, b);
// // Simple while loop
// case.addCompareOutput(
// \\pub export fn main() c_int {
// \\ var count: c_int = 0;
// \\ var opt_ptr: ?*c_int = &count;
// \\ while (opt_ptr) |_| : (count += 1) {
// \\ if (count == 4) opt_ptr = null;
// \\ }
// \\ return count - 5;
// \\}
// , "");
// // Same with non pointer optionals
// case.addCompareOutput(
// \\pub export fn main() c_int {
// \\ var count: c_int = 0;
// \\ var opt_ptr: ?c_int = count;
// \\ while (opt_ptr) |_| : (count += 1) {
// \\ if (count == 4) opt_ptr = null;
// \\ }
// \\ return count - 5;
// \\}
// , "");
//}
{
var case = ctx.exeFromCompiledC("errors", .{}, b);
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var e1 = error.Foo;
\\ var e2 = error.Bar;
\\ assert(e1 != e2);
\\ assert(e1 == error.Foo);
\\ assert(e2 == error.Bar);
\\ return 0;
\\}
\\fn assert(b: bool) void {
\\ if (!b) unreachable;
\\}
, "");
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var e: anyerror!c_int = 0;
\\ const i = e catch 69;
\\ return i;
\\}
, "");
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var e: anyerror!c_int = error.Foo;
\\ const i = e catch 69;
\\ return 69 - i;
\\}
, "");
case.addCompareOutput(
\\const E = error{e};
\\const S = struct { x: u32 };
\\fn f() E!u32 {
\\ const x = (try @as(E!S, S{ .x = 1 })).x;
\\ return x;
\\}
\\pub export fn main() c_int {
\\ const x = f() catch @as(u32, 0);
\\ if (x != 1) unreachable;
\\ return 0;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("structs", .{}, b);
case.addError(
\\const Point = struct { x: i32, y: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .y = 24,
\\ .x = 12,
\\ .y = 24,
\\ };
\\ return p.y - p.x - p.x;
\\}
, &.{
":4:10: error: duplicate struct field name",
":6:10: note: duplicate name here",
":3:21: note: struct declared here",
});
case.addError(
\\const Point = struct { x: i32, y: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .y = 24,
\\ };
\\ return p.y - p.x - p.x;
\\}
, &.{
":3:21: error: missing struct field: x",
":1:15: note: struct 'tmp.Point' declared here",
});
case.addError(
\\const Point = struct { x: i32, y: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .x = 12,
\\ .y = 24,
\\ .z = 48,
\\ };
\\ return p.y - p.x - p.x;
\\}
, &.{
":6:10: error: no field named 'z' in struct 'tmp.Point'",
":1:15: note: struct declared here",
});
case.addCompareOutput(
\\const Point = struct { x: i32, y: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .x = 12,
\\ .y = 24,
\\ };
\\ return p.y - p.x - p.x;
\\}
, "");
case.addCompareOutput(
\\const Point = struct { x: i32, y: i32, z: i32, a: i32, b: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .x = 18,
\\ .y = 24,
\\ .z = 1,
\\ .a = 2,
\\ .b = 3,
\\ };
\\ return p.y - p.x - p.z - p.a - p.b;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("unions", .{}, b);
case.addError(
\\const U = union {
\\ a: u32,
\\ b
\\};
, &.{
":3:5: error: union field missing type",
});
case.addError(
\\const E = enum { a, b };
\\const U = union(E) {
\\ a: u32 = 1,
\\ b: f32 = 2,
\\};
, &.{
":2:11: error: explicitly valued tagged union requires inferred enum tag type",
":3:14: note: tag value specified here",
});
case.addError(
\\const U = union(enum) {
\\ a: u32 = 1,
\\ b: f32 = 2,
\\};
, &.{
":1:11: error: explicitly valued tagged union missing integer tag type",
":2:14: note: tag value specified here",
});
}
{
var case = ctx.exeFromCompiledC("enums", .{}, b);
case.addError(
\\const E1 = packed enum { a, b, c };
\\const E2 = extern enum { a, b, c };
\\export fn foo() void {
\\ _ = E1.a;
\\}
\\export fn bar() void {
\\ _ = E2.a;
\\}
, &.{
":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
});
// comptime and types are caught in AstGen.
case.addError(
\\const E1 = enum {
\\ a,
\\ comptime b,
\\ c,
\\};
\\const E2 = enum {
\\ a,
\\ b: i32,
\\ c,
\\};
\\export fn foo() void {
\\ _ = E1.a;
\\}
\\export fn bar() void {
\\ _ = E2.a;
\\}
, &.{
":3:5: error: enum fields cannot be marked comptime",
":8:8: error: enum fields do not have types",
":6:12: note: consider 'union(enum)' here to make it a tagged union",
});
// @intFromEnum, @enumFromInt, enum literal coercion, field access syntax, comparison, switch
case.addCompareOutput(
\\const Number = enum { One, Two, Three };
\\
\\pub export fn main() c_int {
\\ var number1 = Number.One;
\\ var number2: Number = .Two;
\\ const number3: Number = @enumFromInt(2);
\\ if (number1 == number2) return 1;
\\ if (number2 == number3) return 1;
\\ if (@intFromEnum(number1) != 0) return 1;
\\ if (@intFromEnum(number2) != 1) return 1;
\\ if (@intFromEnum(number3) != 2) return 1;
\\ var x: Number = .Two;
\\ if (number2 != x) return 1;
\\ switch (x) {
\\ .One => return 1,
\\ .Two => return 0,
\\ number3 => return 2,
\\ }
\\}
, "");
// Specifying alignment is a parse error.
// This also tests going from a successful build to a parse error.
case.addError(
\\const E1 = enum {
\\ a,
\\ b align(4),
\\ c,
\\};
\\export fn foo() void {
\\ _ = E1.a;
\\}
, &.{
":3:13: error: enum fields cannot be aligned",
});
// Redundant non-exhaustive enum mark.
// This also tests going from a parse error to an AstGen error.
case.addError(
\\const E1 = enum {
\\ a,
\\ _,
\\ b,
\\ c,
\\ _,
\\};
\\export fn foo() void {
\\ _ = E1.a;
\\}
, &.{
":6:5: error: redundant non-exhaustive enum mark",
":3:5: note: other mark here",
});
case.addError(
\\const E1 = enum {
\\ a,
\\ b,
\\ c,
\\ _ = 10,
\\};
\\export fn foo() void {
\\ _ = E1.a;
\\}
, &.{
":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value",
});
case.addError(
\\const E1 = enum { a, b, _ };
\\export fn foo() void {
\\ _ = E1.a;
\\}
, &.{
":1:12: error: non-exhaustive enum missing integer tag type",
":1:25: note: marked non-exhaustive here",
});
case.addError(
\\const E1 = enum { a, b, c, b, d };
\\pub export fn main() c_int {
\\ _ = E1.a;
\\}
, &.{
":1:22: error: duplicate enum field name",
":1:28: note: duplicate field here",
":1:12: note: enum declared here",
});
case.addError(
\\pub export fn main() c_int {
\\ const a = true;
\\ _ = @intFromEnum(a);
\\}
, &.{
":3:20: error: expected enum or tagged union, found 'bool'",
});
case.addError(
\\pub export fn main() c_int {
\\ const a = 1;
\\ _ = @as(bool, @enumFromInt(a));
\\}
, &.{
":3:19: error: expected enum, found 'bool'",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ _ = @as(E, @enumFromInt(3));
\\}
, &.{
":3:16: error: enum 'tmp.E' has no tag with value '3'",
":1:11: note: enum declared here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .a;
\\ switch (x) {
\\ .a => {},
\\ .c => {},
\\ }
\\}
, &.{
":4:5: error: switch must handle all possibilities",
":1:21: note: unhandled enumeration value: 'b'",
":1:11: note: enum 'tmp.E' declared here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .a;
\\ switch (x) {
\\ .a => {},
\\ .b => {},
\\ .b => {},
\\ .c => {},
\\ }
\\}
, &.{
":7:10: error: duplicate switch value",
":6:10: note: previous value here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .a;
\\ switch (x) {
\\ .a => {},
\\ .b => {},
\\ .c => {},
\\ else => {},
\\ }
\\}
, &.{
":8:14: error: unreachable else prong; all cases already handled",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .a;
\\ switch (x) {
\\ .a => {},
\\ .b => {},
\\ _ => {},
\\ }
\\}
, &.{
":4:5: error: '_' prong only allowed when switching on non-exhaustive enums",
":7:11: note: '_' prong here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ _ = E.d;
\\}
, &.{
":3:11: error: enum 'tmp.E' has no member named 'd'",
":1:11: note: enum declared here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .d;
\\ _ = x;
\\}
, &.{
":3:17: error: no field named 'd' in enum 'tmp.E'",
":1:11: note: enum declared here",
});
}
{
var case = ctx.exeFromCompiledC("shift right and left", .{}, b);
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var i: u32 = 16;
\\ assert(i >> 1, 8);
\\ return 0;
\\}
\\fn assert(a: u32, b: u32) void {
\\ if (a != b) unreachable;
\\}
, "");
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var i: u32 = 16;
\\ assert(i << 1, 32);
\\ return 0;
\\}
\\fn assert(a: u32, b: u32) void {
\\ if (a != b) unreachable;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("inferred error sets", .{}, b);
case.addCompareOutput(
\\pub export fn main() c_int {
\\ if (foo()) |_| {
\\ @panic("test fail");
\\ } else |err| {
\\ if (err != error.ItBroke) {
\\ @panic("test fail");
\\ }
\\ }
\\ return 0;
\\}
\\fn foo() !void {
\\ return error.ItBroke;
\\}
, "");
}
{
// TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64)
var case = ctx.exeFromCompiledC("add and sub wrapping operations", .{}, b);
case.addCompareOutput(
\\pub export fn main() c_int {
\\ // Addition
\\ if (!add_u3(1, 1, 2)) return 1;
\\ if (!add_u3(7, 1, 0)) return 1;
\\ if (!add_i3(1, 1, 2)) return 1;
\\ if (!add_i3(3, 2, -3)) return 1;
\\ if (!add_i3(-3, -2, 3)) return 1;
\\ if (!add_c_int(1, 1, 2)) return 1;
\\ // TODO enable these when stage2 supports std.math.maxInt
\\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1;
\\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1;
\\
\\ // Subtraction
\\ if (!sub_u3(2, 1, 1)) return 1;
\\ if (!sub_u3(0, 1, 7)) return 1;
\\ if (!sub_i3(2, 1, 1)) return 1;
\\ if (!sub_i3(3, -2, -3)) return 1;
\\ if (!sub_i3(-3, 2, 3)) return 1;
\\ if (!sub_c_int(2, 1, 1)) return 1;
\\ // TODO enable these when stage2 supports std.math.maxInt
\\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1;
\\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1;
\\
\\ return 0;
\\}
\\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool {
\\ return expected == lhs +% rhs;
\\}
\\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool {
\\ return expected == lhs +% rhs;
\\}
\\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
\\ return expected == lhs +% rhs;
\\}
\\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool {
\\ return expected == lhs -% rhs;
\\}
\\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool {
\\ return expected == lhs -% rhs;
\\}
\\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
\\ return expected == lhs -% rhs;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("rem", linux_x64, b);
case.addCompareOutput(
\\fn assert(ok: bool) void {
\\ if (!ok) unreachable;
\\}
\\fn rem(lhs: i32, rhs: i32, expected: i32) bool {
\\ return @rem(lhs, rhs) == expected;
\\}
\\pub export fn main() c_int {
\\ assert(rem(-5, 3, -2));
\\ assert(rem(5, 3, 2));
\\ return 0;
\\}
, "");
}
}