lib/choir/src/properties/calls.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const hypothesis = @import("hypothesis");
  3 const choir = @import("choir");
  4 
  5 const Mode = enum {
  6     valid,
  7     operand_count,
  8     operand_type,
  9     result_count,
 10     result_type,
 11 };
 12 
 13 const modes = std.enums.values(Mode);
 14 
 15 fn settings() hypothesis.Settings {
 16     var value = hypothesis.Settings.quick()
 17         .withSeed(0xf00c_ca11_2026_0710)
 18         .withDatabase("zig-out/hypothesis-failures/choir-func-call-signatures");
 19     value.max_examples = 100;
 20     value.target_examples = 100;
 21     return value;
 22 }
 23 
 24 fn drawUsize(conjecture: *hypothesis.ConjectureData, min: usize, max: usize, shrink_towards: usize) !usize {
 25     return @intCast(try conjecture.drawInteger(@intCast(min), @intCast(max), @intCast(shrink_towards)));
 26 }
 27 
 28 fn flipped(ty: choir.Type, i32_type: choir.Type, i64_type: choir.Type) choir.Type {
 29     return if (ty.eql(i32_type)) i64_type else i32_type;
 30 }
 31 
 32 pub const FuncCallSignatureProperty = struct {
 33     pub fn property(conjecture: *hypothesis.ConjectureData, allocator: std.mem.Allocator) !void {
 34         const mode = modes[try drawUsize(conjecture, 0, modes.len - 1, 0)];
 35         const has_body = try drawUsize(conjecture, 0, 1, 0) != 0;
 36         var argument_count = try drawUsize(conjecture, 0, 64, 0);
 37         var result_count = try drawUsize(conjecture, 0, 16, 0);
 38         if (mode == .operand_type and argument_count == 0) argument_count = 1;
 39         if (mode == .result_type and result_count == 0) result_count = 1;
 40         try conjecture.target(argument_count, "function argument count");
 41         try conjecture.target(result_count, "function result count");
 42         try conjecture.target(@backingInt(mode), "signature outcome");
 43 
 44         var ctx = try choir.Context.init(allocator, choir.Context.Limits.testing);
 45         defer ctx.deinit(allocator);
 46         try choir.dialects.registerAllDialects(&ctx);
 47 
 48         const loc = choir.Location.getUnknown();
 49         const i32_type = try choir.dialects.ArithDialect.getI32Type(&ctx);
 50         const i64_type = try choir.dialects.ArithDialect.getScalarType(&ctx, .i64);
 51 
 52         const argument_types = try allocator.alloc(choir.Type, argument_count);
 53         defer allocator.free(argument_types);
 54         for (argument_types) |*ty| {
 55             ty.* = if (try drawUsize(conjecture, 0, 1, 0) == 0) i32_type else i64_type;
 56         }
 57 
 58         const result_types = try allocator.alloc(choir.Type, result_count);
 59         defer allocator.free(result_types);
 60         for (result_types) |*ty| {
 61             ty.* = if (try drawUsize(conjecture, 0, 1, 0) == 0) i32_type else i64_type;
 62         }
 63 
 64         const call_argument_count = if (mode == .operand_count)
 65             if (argument_count == 0) 1 else argument_count - 1
 66         else
 67             argument_count;
 68         const call_argument_types = try allocator.alloc(choir.Type, call_argument_count);
 69         defer allocator.free(call_argument_types);
 70         for (call_argument_types, 0..) |*ty, index| {
 71             ty.* = if (index < argument_types.len) argument_types[index] else i32_type;
 72         }
 73         if (mode == .operand_type) {
 74             const index = try drawUsize(conjecture, 0, call_argument_types.len - 1, 0);
 75             call_argument_types[index] = flipped(call_argument_types[index], i32_type, i64_type);
 76         }
 77 
 78         const call_result_count = if (mode == .result_count)
 79             if (result_count == 0) 1 else result_count - 1
 80         else
 81             result_count;
 82         const call_result_types = try allocator.alloc(choir.Type, call_result_count);
 83         defer allocator.free(call_result_types);
 84         for (call_result_types, 0..) |*ty, index| {
 85             ty.* = if (index < result_types.len) result_types[index] else i32_type;
 86         }
 87         if (mode == .result_type) {
 88             const index = try drawUsize(conjecture, 0, call_result_types.len - 1, 0);
 89             call_result_types[index] = flipped(call_result_types[index], i32_type, i64_type);
 90         }
 91 
 92         const module = try choir.dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
 93         const module_block = module.getBodyBlock();
 94         const callee = if (has_body)
 95             try choir.dialects.FuncDialect.FuncOp.create(&ctx, loc, "callee", argument_types, result_types)
 96         else
 97             try choir.dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "callee", argument_types, result_types);
 98         try module_block.addOperation(callee.op);
 99 
100         var caller = try choir.dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", call_argument_types, &.{});
101         try module_block.addOperation(caller.op);
102         const call_operands = try allocator.alloc(*choir.Value, call_argument_count);
103         defer allocator.free(call_operands);
104         for (call_operands, 0..) |*operand, index| {
105             operand.* = caller.getArgument(index);
106         }
107         const call = try choir.dialects.FuncDialect.CallOp.create(
108             &ctx,
109             loc,
110             "callee",
111             call_operands,
112             call_result_types,
113         );
114         try caller.getEntryBlock().addOperation(call.op);
115 
116         const result = choir.ir.verifyOperation(module.op, choir.ir.verify.default_options);
117         switch (mode) {
118             .valid => try result,
119             .operand_count => try std.testing.expectError(choir.dialects.func.FuncVerifyError.CallOperandCountMismatch, result),
120             .operand_type => try std.testing.expectError(choir.dialects.func.FuncVerifyError.CallOperandTypeMismatch, result),
121             .result_count => try std.testing.expectError(choir.dialects.func.FuncVerifyError.CallResultCountMismatch, result),
122             .result_type => try std.testing.expectError(choir.dialects.func.FuncVerifyError.CallResultTypeMismatch, result),
123         }
124     }
125 };
126 
127 test "property: func calls verify generated body and declaration signatures" {
128     try hypothesis.checkNamed(FuncCallSignatureProperty, "choir-func-call-signatures", settings());
129 }