3

I've recently started learning Zig. As a little project I wanted to implement a small QuickCheck [1] style helper library for writing randomized tests.

However, I can't figure out how to write a generic way to call a function with an arbitrary number of arguments.

Here's a simplified version that can test functions with two arguments:

const std    = @import("std");
const Prng   = std.rand.DefaultPrng;
const Random = std.rand.Random;
const expect = std.testing.expect;

// the thing we want to test
fn some_property(a: u64, b: u64) !void {
    var tmp: u64 = undefined;
    var c1 = @addWithOverflow(u64, a, b, &tmp);
    var c2 = @addWithOverflow(u64, a, b, &tmp);

    expect(c1 == c2);
}

// helper for generating random arguments for the function under test
fn gen(comptime T: ?type, rnd: Random) (T orelse undefined) {
    switch (T orelse undefined) {
        u64  => return rnd.int(u64),
        f64  => return rnd.float(f64),
        else => @compileError("unsupported type"),
    }
}

/// tests if 'property' holds.
fn for_all(property: anytype) !void {
  var rnd = Prng.init(0);

  const arg_types = @typeInfo(@TypeOf(property)).Fn.args;

  var i: usize = 0;
  while (i < 100) {
    var a = gen(arg_types[0].arg_type, rnd.random());
    var b = gen(arg_types[1].arg_type, rnd.random());

    var args = .{a, b}; // <-- how do I build args for functions with any number of arguments?

    try @call(.{}, property, args);

    i += 1;
  }
}

test "test" {
  try for_all(some_property);
}

I've tried a few different things, but I can't figure out how to get the above code to work for functions with any number of arguments.

Things I've tried:

  • Make args an array and fill it with an inline for loop. Doesn't work since []anytype is not a valid type.
  • Use a bit of comptime magic to build a struct type whose fields hold the arguments for @call. This hits a TODO in the compiler: error: TODO: struct args.
  • Write generic functions that return an appropriate argument tuple call. I don't really like this one, since you need one function for every arity you want to support. But it doesn't seem to work anyway since antype is not a valid return type.

I'm on Zig 0.9.1.

Any insight would be appreciated.

[1] https://hackage.haskell.org/package/QuickCheck

1 Answer 1

2

This can be done with std.meta.ArgsTuple (defined in this file of the zig standard library)

    const Args = std.meta.ArgsTuple(@TypeOf(property));

    var i: usize = 0;
    while (i < 1000) : (i += 1) {
        var args: Args = undefined;
        inline for (std.meta.fields(Args)) |field, index| {
            args[index] = gen(field.field_type, rnd.random());
        }

        try @call(.{}, property, args);
    }

The way this works internally is it constructs a tuple type with @Type(). We can then fill it with values and use it to call the function.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.