Sometimes, tests using compile-time literals can mislead API designers, making a function that actually fails to compile at runtime appear to work correctly under test.
This is a buggy inline function that expects an integer and a floating-point number to be passed in, multiplies them, and outputs an integer. This function fails to compile correctly for runtime values. However, when tested with literal values, this bug is not exposed, and the test runs fine.
fn FloatIntToIntType(Ta: type, Tb: type) type {
const a_is_int = switch (@typeInfo(Ta)) {
.int, .comptime_int => true,
.float, .comptime_float => false,
else => unreachable,
};
const b_is_int = switch (@typeInfo(Tb)) {
.int, .comptime_int => true,
.float, .comptime_float => false,
else => unreachable,
};
if (a_is_int and b_is_int) return @TypeOf(Ta, Tb) else if (a_is_int) return Ta else return Tb;
}
inline fn floatMulIntToInt(a: anytype, b: anytype) !FloatIntToIntType(@TypeOf(a), @TypeOf(b)) {
const result = a * b;
const ResultType = FloatIntToIntType(@TypeOf(a), @TypeOf(b));
if (!std.math.isFinite(result) or result > std.math.maxInt(ResultType) or result < std.math.minInt(ResultType)) return error.Overflow;
return @as(ResultType, @intFromFloat(result));
}
test floatMulIntToInt {
const a: usize = 1437;
const b: f32 = 1.2;
try std.testing.expectEqual(1724, floatMulIntToInt(a, b));
}
My personal solution is to introduce a runtimeValue. I am not sure if my implementation is the best one, maybe there is a better one?
fn runtimeValue(T: type, v: anytype) T {
var ret: T = undefined;
@as(*volatile T, &ret).* = v;
return ret;
}
The test based on runtimeValue() can correctly check the compilation errors of this inline function for runtime values.
test floatMulIntToInt {
const a = runtimeValue(usize, 1437);
const b = runtimeValue(f32, 1.2);
try std.testing.expectEqual(1724, floatMulIntToInt(a, b));
}
test.zig:266:22: error: incompatible types: 'usize' and 'f32'
const result = a * b;
~~^~~
test.zig:266:20: note: type 'usize' here
const result = a * b;
^
test.zig:266:24: note: type 'f32' here
const result = a * b;
^
test.zig:275:55: note: called inline here
try std.testing.expectEqual(1724, floatMulIntToInt(a, b));
~~~~~~~~~~~~~~~~^~~~~~
I’m not sure if there’s a function in the current standard library that does something similar to runtimeValue. If not, I think it’s definitely worth adding to std.testing