Comptime Interface testing

Here is a start of comptime code that will test a struct to see if it contains all the fields and delcs of another struct. I ideally you also want to extend this for unions, error sets, and all other types. Function parameters might also have interfaces in them too that need to be recursively checked. This works for basic types and is a basic start:

const Iface = struct {
    b: u32,
    fn funca(_: u8, _: u32) bool {
        return true;
    }
    fn funcb(_: []u8) u32 {
        return 0;
    }
};

const Class = struct {
    a: u32,
    b: u32,

    fn funca(_: u8, _: u32) bool {
        return true;
    }
    fn funcc() void {}
    fn funcb(_: []u8) u32 {
        return 0;
    }
};

fn get_field(comptime class: type, comptime name: []const u8) ?std.builtin.Type.StructField {
    const ti = @typeInfo(class).Struct;
    inline for (ti.fields) |f| {
        if (std.mem.eql(u8, name, f.name)) {
            return f;
        }
    }
    return null;
}

fn has_interface(comptime iface: type, comptime class: type) bool {
    const iti = @typeInfo(iface).Struct;
    inline for (iti.decls) |i| {
        const x = @field(iface, i);
        const y = @field(class, i);
        if (!std.meta.eql(@TypeOf(x), @TypeOf(y)))
            return false;
    }
    inline for (iti.fields) |i| {
        const fi = get_field(class, i.name);
        if (fi == null or !std.meta.eql(i.type, fi.?.type))
            return false;
    }
    return true;
}

test "same" {
    std.debug.print("\n{}\n", .{has_interface(Iface, Class)});
}

You could even add a see also to documentation to point to the interface type. (I still want interfaces in the language, but this is kind of neat)

I don’t know if this answers the challenge…

const std = @import("std");

pub fn enforceInterface(comptime iface: type, comptime T: type) type {
    const iface_info = @typeInfo(iface);

    if (iface_info != .Struct or @typeInfo(T) != .Struct) {
        @compileError("enforceInterface accepts only structs as arguments");
    }

    inline for (iface_info.Struct.decls) |iface_decl| {
        const iface_decl_type = @TypeOf(@field(iface, iface_decl.name));

        if (!@hasDecl(T, iface_decl.name)) {
            @compileError(std.fmt.comptimePrint("missing public declaration '{s}.{s}' of type '{s}'", .{ @typeName(T), iface_decl.name, @typeName(iface_decl_type) }));
        }

        const T_decl_type = @TypeOf(@field(T, iface_decl.name));
        const iface_decl_info = @typeInfo(iface_decl_type);
        const T_decl_info = @typeInfo(T_decl_type);

        if (iface_decl_info == .Fn and T_decl_info == .Fn) {
            const iface_return_type = @typeInfo(iface_decl_type).Fn.return_type;
            const T_return_type = @typeInfo(T_decl_type).Fn.return_type;

            if (iface_return_type != T_return_type and iface_return_type != null) {
                const iface_rt_info = @typeInfo(iface_return_type.?);
                const T_rt_info = @typeInfo(T_return_type.?);
                if (iface_rt_info == .ErrorUnion and T_rt_info == .ErrorUnion) {
                    if (iface_rt_info.ErrorUnion.payload == T_rt_info.ErrorUnion.payload) {
                        continue;
                    }
                }
            }
        }

        if (iface_decl_type != T_decl_type) {
            @compileError(std.fmt.comptimePrint("expected type '{s}', found '{s}' for declaration of '{s}.{s}'", .{ @typeName(iface_decl_type), @typeName(T_decl_type), @typeName(T), iface_decl.name }));
        }
    }
    return T;
}

Usage:

const Iface = @import("iface.zig");
const TestStruct = enforceInterface(Iface, @import("teststruct.zig"));