lib/choir/src/properties/scalars.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const hypothesis = @import("hypothesis");
3 const choir = @import("choir");
4
5 const ScalarKind = choir.dialects.arith.ScalarKind;
6 const scalar_kinds = std.enums.values(ScalarKind);
7
8 fn settings() hypothesis.Settings {
9 var value = hypothesis.Settings.quick()
10 .withSeed(0x5ca1_a2c1_2026_0710)
11 .withDatabase("zig-out/hypothesis-failures/choir-arith-scalar-classifier");
12 value.max_examples = 100;
13 value.target_examples = 100;
14 return value;
15 }
16
17 fn drawUsize(conjecture: *hypothesis.ConjectureData, min: usize, max: usize, shrink_towards: usize) !usize {
18 return @intCast(try conjecture.drawInteger(@intCast(min), @intCast(max), @intCast(shrink_towards)));
19 }
20
21 fn referenceTypeName(name: []const u8) ?ScalarKind {
22 inline for (scalar_kinds) |kind| {
23 if (std.mem.eql(u8, name, choir.dialects.arith.scalarTypeName(kind))) return kind;
24 }
25 return null;
26 }
27
28 fn referenceSuffix(suffix: []const u8) ?ScalarKind {
29 inline for (scalar_kinds) |kind| {
30 if (std.mem.eql(u8, suffix, choir.dialects.arith.scalarTypeSuffix(kind))) return kind;
31 }
32 return null;
33 }
34
35 fn mutateCanonical(
36 conjecture: *hypothesis.ConjectureData,
37 source: []const u8,
38 buffer: []u8,
39 ) ![]const u8 {
40 @memcpy(buffer[0..source.len], source);
41 const mode = try drawUsize(conjecture, 0, 3, 0);
42 return switch (mode) {
43 0 => blk: {
44 const index = try drawUsize(conjecture, 0, source.len - 1, 0);
45 const replacement: u8 = @intCast(try drawUsize(conjecture, 0, 255, 0));
46 buffer[index] = if (replacement == buffer[index]) replacement +% 1 else replacement;
47 break :blk buffer[0..source.len];
48 },
49 1 => buffer[0..try drawUsize(conjecture, 0, source.len - 1, 0)],
50 2 => blk: {
51 buffer[source.len] = @intCast(try drawUsize(conjecture, 0, 255, 0));
52 break :blk buffer[0 .. source.len + 1];
53 },
54 3 => blk: {
55 std.mem.copyBackwards(u8, buffer[1 .. source.len + 1], buffer[0..source.len]);
56 buffer[0] = @intCast(try drawUsize(conjecture, 0, 255, 0));
57 break :blk buffer[0 .. source.len + 1];
58 },
59 else => unreachable,
60 };
61 }
62
63 pub const ScalarClassifierProperty = struct {
64 pub fn property(conjecture: *hypothesis.ConjectureData, allocator: std.mem.Allocator) !void {
65 var ctx = try choir.Context.init(allocator, choir.Context.Limits.testing);
66 defer ctx.deinit(allocator);
67 try choir.dialects.registerAllDialects(&ctx);
68
69 inline for (scalar_kinds) |kind| {
70 const name = choir.dialects.arith.scalarTypeName(kind);
71 const suffix = choir.dialects.arith.scalarTypeSuffix(kind);
72 try std.testing.expectEqual(kind, choir.dialects.arith.scalarKindFromTypeName(name).?);
73 try std.testing.expectEqual(kind, choir.dialects.arith.scalarKindFromSuffix(suffix).?);
74 const ty = try ctx.getDialectTypeFromName(name);
75 try std.testing.expectEqual(kind, choir.dialects.arith.scalarKindFromType(ty).?);
76 }
77
78 var arbitrary_buffer: [64]u8 = undefined;
79 const arbitrary_len = try drawUsize(conjecture, 0, arbitrary_buffer.len, 0);
80 for (arbitrary_buffer[0..arbitrary_len]) |*byte| {
81 byte.* = @intCast(try drawUsize(conjecture, 0, 255, 0));
82 }
83 const arbitrary = arbitrary_buffer[0..arbitrary_len];
84 try conjecture.target(arbitrary_len, "arbitrary name length");
85 try std.testing.expectEqual(referenceTypeName(arbitrary), choir.dialects.arith.scalarKindFromTypeName(arbitrary));
86 try std.testing.expectEqual(referenceSuffix(arbitrary), choir.dialects.arith.scalarKindFromSuffix(arbitrary));
87
88 const field_index = try drawUsize(conjecture, 0, scalar_kinds.len - 1, 0);
89 const kind = scalar_kinds[field_index];
90 var mutated_name_buffer: [32]u8 = undefined;
91 var mutated_suffix_buffer: [32]u8 = undefined;
92 const mutated_name = try mutateCanonical(
93 conjecture,
94 choir.dialects.arith.scalarTypeName(kind),
95 &mutated_name_buffer,
96 );
97 const mutated_suffix = try mutateCanonical(
98 conjecture,
99 choir.dialects.arith.scalarTypeSuffix(kind),
100 &mutated_suffix_buffer,
101 );
102 try std.testing.expectEqual(referenceTypeName(mutated_name), choir.dialects.arith.scalarKindFromTypeName(mutated_name));
103 try std.testing.expectEqual(referenceSuffix(mutated_suffix), choir.dialects.arith.scalarKindFromSuffix(mutated_suffix));
104 }
105 };
106
107 test "property: arith scalar classifiers accept only canonical names" {
108 try hypothesis.checkNamed(ScalarClassifierProperty, "choir-arith-scalar-classifier", settings());
109 }