Hello everyone!
Can you help me understand why this test does not pass? (master branch)
Context: I wanted to make sure a struct (eg a user generated struct or a reificated struct) has just attributes named “required” and “commands”. If the struct has a field called “hello” the test should not pass.
Test 1: I iterate over the fields and check with std.mem.eql
const std = @import("std");
const Target = struct {
required: u32 = 0,
commands: u32 = 0,
// imagine a user has written an invalid field here!
};
test "Checking with an if (this test _should_ pass)" {
const info = @typeInfo(Target);
// let's iterate over the struct to see if just required or commands are valid
inline for (info.@"struct".fields) |field| {
const name = field.name;
var hasRequired = false;
var hasCommands = false;
if (std.mem.eql(u8, name, "required")) {
hasRequired = true;
} else if (std.mem.eql(u8, name, "commands")) {
hasCommands = true;
} else {
@compileLog("Field Bytes:");
inline for (name) |c| { @compileLog(c); }
@compileLog("Expected Bytes:");
inline for ("required") |c| { @compileLog(c); }
@compileError("I've found an invalid comparison: " ++ name);
}
}
}
Output: it fails, but isn’t the string the same???
bug.zig:45:13: error: I've found an invalid comparison: required
@compileError("I've found an invalid comparison: " ++ name);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compile Log Output:
@as(*const [12:0]u8, "Field Bytes:")
@as(u8, 114)
@as(u8, 101)
@as(u8, 113)
@as(u8, 117)
@as(u8, 105)
@as(u8, 114)
@as(u8, 101)
@as(u8, 100)
@as(*const [15:0]u8, "Expected Bytes:")
@as(u8, 114)
@as(u8, 101)
@as(u8, 113)
@as(u8, 117)
@as(u8, 105)
@as(u8, 114)
@as(u8, 101)
@as(u8, 100)
At this point I was super paranoid, so I implemented a one by one string comparison function
fn verboseStrEq(comptime field_name: []const u8, comptime target: []const u8) bool {
if (field_name.len != target.len) return false;
for (field_name, 0..) |char, i| {
if (char != target[i]) {
@compileLog("MISMATCH AT INDEX:", i);
@compileLog(" Field char:", char);
@compileLog(" Target char:", target[i]);
return false;
}
}
return true;
}
test "Doing it with a custom string to see why it did not pass" {
const info = @typeInfo(Target);
inline for (info.@"struct".fields) |field| {
const name = field.name;
var hasRequired = false;
var hasCommands = false;
if (std.mem.eql(u8, name, "required")) {
hasRequired = true;
} else if (std.mem.eql(u8, name, "commands")) {
hasCommands = true;
} else {
@compileLog("Checking field:", name);
if (verboseStrEq(name, "required")) {
@compileLog("Match!");
} else {
@compileLog("No Match!");
@compileError("Comparison failed despite looking identical");
}
}
}
}
Output: if just got me even more confused, how the f*ck is does this match and not match within the same run???
bug.zig:70:18: error: Comparison failed despite looking identical
@compileError("Comparison failed despite looking identical");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compile Log Output:
@as(*const [18:0]u8, "MISMATCH AT INDEX:"), @as(usize, [runtime value])
@as(*const [13:0]u8, " Field char:"), @as(u8, [runtime value])
@as(*const [14:0]u8, " Target char:"), @as(u8, [runtime value])
@as(*const [15:0]u8, "Checking field:"), @as([:0]const u8, "required"[0..8])
@as(*const [6:0]u8, "Match!")
@as(*const [9:0]u8, "No Match!")
The only reasonable behaviour (and in essence is doning the same but at runtime if I am not wrong?) happens in this last text, where it just passes.
test "With field parent it works" {
const hasRequired = @hasField(Target, "required");
const hasCommands = @hasField(Target, "commands");
try std.testing.expect(hasRequired);
try std.testing.expect(hasCommands);
}
This feels like a bug to me but I don’t understand why nor how happens. If you also think it is I can report it to codeberg, but I want to make sure I am not doing something deeply wrong.
Thank you ![]()