const builtin = @import("builtin"); const std = @import("std"); const expect = std.testing.expect; fn add(args: anytype) i32 { var sum = @as(i32, 0); { comptime var i: usize = 0; inline while (i < args.len) : (i += 1) { sum += args[i]; } } return sum; } test "add arbitrary args" { try expect(add(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10); try expect(add(.{@as(i32, 1234)}) == 1234); try expect(add(.{}) == 0); } fn readFirstVarArg(args: anytype) void { _ = args[0]; } test "send void arg to var args" { readFirstVarArg(.{{}}); } test "pass args directly" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try expect(addSomeStuff(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10); try expect(addSomeStuff(.{@as(i32, 1234)}) == 1234); try expect(addSomeStuff(.{}) == 0); } fn addSomeStuff(args: anytype) i32 { return add(args); } test "runtime parameter before var args" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try expect((try extraFn(10, .{})) == 0); try expect((try extraFn(10, .{false})) == 1); try expect((try extraFn(10, .{ false, true })) == 2); comptime { try expect((try extraFn(10, .{})) == 0); try expect((try extraFn(10, .{false})) == 1); try expect((try extraFn(10, .{ false, true })) == 2); } } fn extraFn(extra: u32, args: anytype) !usize { _ = extra; if (args.len >= 1) { try expect(args[0] == false); } if (args.len >= 2) { try expect(args[1] == true); } return args.len; } const foos = [_]fn (anytype) bool{ foo1, foo2, }; fn foo1(args: anytype) bool { _ = args; return true; } fn foo2(args: anytype) bool { _ = args; return false; } test "array of var args functions" { try expect(foos[0](.{})); try expect(!foos[1](.{})); } test "pass zero length array to var args param" { doNothingWithFirstArg(.{""}); } fn doNothingWithFirstArg(args: anytype) void { _ = args[0]; } test "simple variadic function" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO 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; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) { // https://github.com/ziglang/zig/issues/14096 return error.SkipZigTest; } if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064 const S = struct { fn simple(...) callconv(.c) c_int { var ap = @cVaStart(); defer @cVaEnd(&ap); return @cVaArg(&ap, c_int); } fn compatible(_: c_int, ...) callconv(.c) c_int { var ap = @cVaStart(); defer @cVaEnd(&ap); return @cVaArg(&ap, c_int); } fn add(count: c_int, ...) callconv(.c) c_int { var ap = @cVaStart(); defer @cVaEnd(&ap); var i: usize = 0; var sum: c_int = 0; while (i < count) : (i += 1) { sum += @cVaArg(&ap, c_int); } return sum; } }; if (builtin.zig_backend != .stage2_c) { // pre C23 doesn't support varargs without a preceding runtime arg. try std.testing.expectEqual(@as(c_int, 0), S.simple(@as(c_int, 0))); try std.testing.expectEqual(@as(c_int, 1024), S.simple(@as(c_int, 1024))); } try std.testing.expectEqual(@as(c_int, 0), S.compatible(undefined, @as(c_int, 0))); try std.testing.expectEqual(@as(c_int, 1024), S.compatible(undefined, @as(c_int, 1024))); try std.testing.expectEqual(@as(c_int, 0), S.add(0)); try std.testing.expectEqual(@as(c_int, 1), S.add(1, @as(c_int, 1))); try std.testing.expectEqual(@as(c_int, 3), S.add(2, @as(c_int, 1), @as(c_int, 2))); { // Test type coercion of a var args argument. // Originally reported at https://github.com/ziglang/zig/issues/16197 var runtime: bool = true; var a: i32 = 1; var b: i32 = 2; _ = .{ &runtime, &a, &b }; try expect(1 == S.add(1, if (runtime) a else b)); } } test "coerce reference to var arg" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO 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; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) { // https://github.com/ziglang/zig/issues/14096 return error.SkipZigTest; } if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350 const S = struct { fn addPtr(count: c_int, ...) callconv(.c) c_int { var ap = @cVaStart(); defer @cVaEnd(&ap); var i: usize = 0; var sum: c_int = 0; while (i < count) : (i += 1) { sum += @cVaArg(&ap, *c_int).*; } return sum; } }; // Originally reported at https://github.com/ziglang/zig/issues/17494 var a: i32 = 12; var b: i32 = 34; try expect(46 == S.addPtr(2, &a, &b)); } test "variadic functions" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO 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_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) { // https://github.com/ziglang/zig/issues/14096 return error.SkipZigTest; } if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064 const S = struct { fn printf(buffer: [*]u8, format: [*:0]const u8, ...) callconv(.c) void { var ap = @cVaStart(); defer @cVaEnd(&ap); vprintf(buffer, format, &ap); } fn vprintf(buffer: [*]u8, format: [*:0]const u8, ap: *std.builtin.VaList) callconv(.c) void { var i: usize = 0; for (format[0..3]) |byte| switch (byte) { 's' => { const arg = @cVaArg(ap, [*:0]const u8); buffer[i..][0..5].* = arg[0..5].*; i += 5; }, 'd' => { const arg = @cVaArg(ap, c_int); switch (arg) { 1 => { buffer[i] = '1'; i += 1; }, 5 => { buffer[i] = '5'; i += 1; }, else => unreachable, } }, else => unreachable, }; } }; var buffer: [7]u8 = undefined; S.printf(&buffer, "dsd", @as(c_int, 1), @as([*:0]const u8, "hello"), @as(c_int, 5)); try expect(std.mem.eql(u8, &buffer, "1hello5")); } test "copy VaList" { 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; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) { // https://github.com/ziglang/zig/issues/14096 return error.SkipZigTest; } if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064 const S = struct { fn add(count: c_int, ...) callconv(.c) c_int { var ap = @cVaStart(); defer @cVaEnd(&ap); var copy = @cVaCopy(&ap); defer @cVaEnd(©); var i: usize = 0; var sum: c_int = 0; while (i < count) : (i += 1) { sum += @cVaArg(&ap, c_int); sum += @cVaArg(©, c_int) * 2; } return sum; } }; try std.testing.expectEqual(@as(c_int, 0), S.add(0)); try std.testing.expectEqual(@as(c_int, 3), S.add(1, @as(c_int, 1))); try std.testing.expectEqual(@as(c_int, 9), S.add(2, @as(c_int, 1), @as(c_int, 2))); } test "unused VaList arg" { 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; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) { // https://github.com/ziglang/zig/issues/14096 return error.SkipZigTest; } if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) { // https://github.com/ziglang/zig/issues/16961 return error.SkipZigTest; // TODO } if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064 const S = struct { fn thirdArg(dummy: c_int, ...) callconv(.c) c_int { _ = dummy; var ap = @cVaStart(); defer @cVaEnd(&ap); _ = @cVaArg(&ap, c_int); return @cVaArg(&ap, c_int); } }; const x = S.thirdArg(0, @as(c_int, 1), @as(c_int, 2)); try std.testing.expectEqual(@as(c_int, 2), x); }