const builtin = @import("builtin");
const std = @import("std.zig");
const debug = std.debug;
const mem = std.mem;
const math = std.math;
const testing = std.testing;
const root = @import("root");

pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags;

const Type = std.lang.Type;

test {
    _ = TrailerFlags;
}

/// Returns the variant of an enum type corresponding to the provided tag name,
/// or `null` if no such variant exists.
pub fn stringToEnum(comptime T: type, tag_name: []const u8) ?T {
    return std.StaticStringMap(T).initEnum().get(tag_name);
}

test stringToEnum {
    const E1 = enum { A, B };
    try testing.expect(E1.A == stringToEnum(E1, "A").?);
    try testing.expect(E1.B == stringToEnum(E1, "B").?);
    try testing.expect(null == stringToEnum(E1, "C"));
}

/// Returns the alignment of type T.
/// Note that if T is a pointer type the result is different than the one
/// returned by @alignOf(T).
/// If T is a pointer type the alignment of the type it points to is returned.
pub fn alignment(comptime T: type) comptime_int {
    return switch (@typeInfo(T)) {
        .optional => |info| switch (@typeInfo(info.child)) {
            .pointer, .@"fn" => alignment(info.child),
            else => @alignOf(T),
        },
        .pointer => |info| info.attrs.@"align" orelse @alignOf(info.child),
        else => @alignOf(T),
    };
}

test alignment {
    try testing.expect(alignment(u8) == 1);
    try testing.expect(alignment(*align(1) u8) == 1);
    try testing.expect(alignment(*align(2) u8) == 2);
    try testing.expect(alignment([]align(1) u8) == 1);
    try testing.expect(alignment([]align(2) u8) == 2);
    try testing.expect(alignment(fn () void) > 0);
    try testing.expect(alignment(*const fn () void) > 0);
    try testing.expect(alignment(*align(128) const fn () void) == 128);
}

/// Given a parameterized type (array, vector, pointer, optional), returns the "child type".
pub fn Child(comptime T: type) type {
    return switch (@typeInfo(T)) {
        .array => |info| info.child,
        .vector => |info| info.child,
        .pointer => |info| info.child,
        .optional => |info| info.child,
        else => @compileError("Expected pointer, optional, array or vector type, found '" ++ @typeName(T) ++ "'"),
    };
}

test Child {
    try testing.expect(Child([1]u8) == u8);
    try testing.expect(Child(*u8) == u8);
    try testing.expect(Child([]u8) == u8);
    try testing.expect(Child(?u8) == u8);
    try testing.expect(Child(@Vector(2, u8)) == u8);
}

/// Given a "memory span" type (array, slice, vector, or pointer to such), returns the "element type".
pub fn Elem(comptime T: type) type {
    switch (@typeInfo(T)) {
        .array => |info| return info.child,
        .vector => |info| return info.child,
        .pointer => |info| switch (info.size) {
            .one => switch (@typeInfo(info.child)) {
                .array => |array_info| return array_info.child,
                .vector => |vector_info| return vector_info.child,
                else => {},
            },
            .many, .c, .slice => return info.child,
        },
        .optional => |info| return Elem(info.child),
        else => {},
    }
    @compileError("Expected pointer, slice, array or vector type, found '" ++ @typeName(T) ++ "'");
}

test Elem {
    try testing.expect(Elem([1]u8) == u8);
    try testing.expect(Elem([*]u8) == u8);
    try testing.expect(Elem([]u8) == u8);
    try testing.expect(Elem(*[10]u8) == u8);
    try testing.expect(Elem(@Vector(2, u8)) == u8);
    try testing.expect(Elem(*@Vector(2, u8)) == u8);
    try testing.expect(Elem(?[*]u8) == u8);
}

/// Given a type which can have a sentinel e.g. `[:0]u8`, returns the sentinel value,
/// or `null` if there is not one.
/// Types which cannot possibly have a sentinel will be a compile error.
/// Result is always comptime-known.
pub inline fn sentinel(comptime T: type) ?Elem(T) {
    switch (@typeInfo(T)) {
        .array => |info| return info.sentinel(),
        .pointer => |info| {
            switch (info.size) {
                .many, .slice => return info.sentinel(),
                .one => switch (@typeInfo(info.child)) {
                    .array => |array_info| return array_info.sentinel(),
                    else => {},
                },
                else => {},
            }
        },
        else => {},
    }
    @compileError("type '" ++ @typeName(T) ++ "' cannot possibly have a sentinel");
}

test sentinel {
    try testSentinel();
    try comptime testSentinel();
}

fn testSentinel() !void {
    try testing.expectEqual(@as(u8, 0), sentinel([:0]u8).?);
    try testing.expectEqual(@as(u8, 0), sentinel([*:0]u8).?);
    try testing.expectEqual(@as(u8, 0), sentinel([5:0]u8).?);
    try testing.expectEqual(@as(u8, 0), sentinel(*const [5:0]u8).?);

    try testing.expect(sentinel([]u8) == null);
    try testing.expect(sentinel([*]u8) == null);
    try testing.expect(sentinel([5]u8) == null);
    try testing.expect(sentinel(*const [5]u8) == null);
}

/// Given a "memory span" type, returns the same type except with the given sentinel value.
pub fn Sentinel(comptime T: type, comptime sentinel_val: Elem(T)) type {
    switch (@typeInfo(T)) {
        .pointer => |info| switch (info.size) {
            .one => switch (@typeInfo(info.child)) {
                .array => |array_info| return @Pointer(
                    .one,
                    info.attrs,
                    [array_info.len:sentinel_val]array_info.child,
                    null,
                ),
                else => {},
            },
            .many, .slice => |size| return @Pointer(size, info.attrs, info.child, sentinel_val),
            else => {},
        },
        .optional => |info| switch (@typeInfo(info.child)) {
            .pointer => |ptr_info| switch (ptr_info.size) {
                .many => return ?@Pointer(.many, ptr_info.attrs, ptr_info.child, sentinel_val),
                else => {},
            },
            else => {},
        },
        else => {},
    }
    @compileError("Unable to derive a sentinel pointer type from " ++ @typeName(T));
}

pub fn containerLayout(comptime T: type) Type.ContainerLayout {
    return switch (@typeInfo(T)) {
        .@"struct" => |info| info.layout,
        .@"union" => |info| info.layout,
        else => @compileError("expected struct or union type, found '" ++ @typeName(T) ++ "'"),
    };
}

test containerLayout {
    const S1 = struct {};
    const S2 = packed struct {};
    const S3 = extern struct {};
    const U1 = union {
        a: u8,
    };
    const U2 = packed union {
        a: u8,
    };
    const U3 = extern union {
        a: u8,
    };

    try testing.expect(containerLayout(S1) == .auto);
    try testing.expect(containerLayout(S2) == .@"packed");
    try testing.expect(containerLayout(S3) == .@"extern");
    try testing.expect(containerLayout(U1) == .auto);
    try testing.expect(containerLayout(U2) == .@"packed");
    try testing.expect(containerLayout(U3) == .@"extern");
}

/// Instead of this function, prefer to use e.g. `@typeInfo(foo).@"struct".decl_names`
/// directly when you know what kind of type it is.
pub fn declarations(comptime T: type) []const [:0]const u8 {
    return switch (@typeInfo(T)) {
        .@"struct" => |info| info.decl_names,
        .@"enum" => |info| info.decl_names,
        .@"union" => |info| info.decl_names,
        .@"opaque" => |info| info.decl_names,
        else => @compileError("Expected struct, enum, union, or opaque type, found '" ++ @typeName(T) ++ "'"),
    };
}

test declarations {
    const E1 = enum {
        A,

        pub fn a() void {}
    };
    const S1 = struct {
        pub fn a() void {}
    };
    const U1 = union {
        b: u8,

        pub fn a() void {}
    };
    const O1 = opaque {
        pub fn a() void {}
    };

    const decls = comptime [_][]const [:0]const u8{
        declarations(E1),
        declarations(S1),
        declarations(U1),
        declarations(O1),
    };

    inline for (decls) |decl| {
        try testing.expect(decl.len == 1);
        try testing.expect(comptime mem.eql(u8, decl[0], "a"));
    }
}

/// To be removed after Zig 0.17.0 is tagged.
pub const declarationInfo = @compileError("Deprecated; use '@hasDecl' instead");
/// To be removed after Zig 0.17.0 is tagged.
pub const fields = @compileError("Deprecated; use 'fieldNames' and 'fieldTypes' instead");

pub fn fieldInfo(comptime T: type, comptime field: FieldEnum(T)) switch (@typeInfo(T)) {
    .@"struct" => struct { name: [:0]const u8, type: type, attrs: Type.Struct.FieldAttributes },
    .@"union" => struct { name: [:0]const u8, type: type, attrs: Type.Union.FieldAttributes },
    .@"enum" => struct { name: [:0]const u8, value: comptime_int },
    .error_set => struct { name: [:0]const u8 },
    else => @compileError("Expected struct, union, error set or enum type, found '" ++ @typeName(T) ++ "'"),
} {
    const idx = @backingInt(field);
    return switch (@typeInfo(T)) {
        .@"struct" => |info| .{
            .name = info.field_names[idx],
            .type = info.field_types[idx],
            .attrs = info.field_attrs[idx],
        },
        .@"union" => |info| .{
            .name = info.field_names[idx],
            .type = info.field_types[idx],
            .attrs = info.field_attrs[idx],
        },
        .@"enum" => |info| .{
            .name = info.field_names[idx],
            .value = info.field_values[idx],
        },
        .error_set => |info| .{ .name = info.error_names.?[idx] },
        else => @compileError("Expected struct, union, error set or enum type, found '" ++ @typeName(T) ++ "'"),
    };
}

test fieldInfo {
    const E1 = enum {
        A,
    };
    const E2 = error{A};
    const S1 = struct {
        a: u8,
    };
    const U1 = union {
        a: u8,
    };

    const e1f = fieldInfo(E1, .A);
    const e2f = fieldInfo(E2, .A);
    const sf = fieldInfo(S1, .a);
    const uf = fieldInfo(U1, .a);

    try testing.expect(mem.eql(u8, e1f.name, "A"));
    try testing.expect(mem.eql(u8, e2f.name, "A"));
    try testing.expect(mem.eql(u8, sf.name, "a"));
    try testing.expect(mem.eql(u8, uf.name, "a"));
    try testing.expect(comptime sf.type == u8);
    try testing.expect(comptime uf.type == u8);
}

pub fn fieldNames(comptime T: type) []const [:0]const u8 {
    return switch (@typeInfo(T)) {
        .@"struct" => |s| s.field_names,
        .@"union" => |u| u.field_names,
        .@"enum" => |e| e.field_names,
        .error_set => |es| es.error_names.?,
        else => @compileError("Expected struct, union, error set or enum type, found '" ++ @typeName(T) ++ "'"),
    };
}

test fieldNames {
    const E1 = enum { A, B };
    const E2 = error{A};
    const S1 = struct {
        a: u8,
    };
    const U1 = union {
        a: u8,
        b: void,
    };

    const e1names = fieldNames(E1);
    const e2names = fieldNames(E2);
    const s1names = fieldNames(S1);
    const u1names = fieldNames(U1);

    try testing.expect(e1names.len == 2);
    try testing.expectEqualSlices(u8, e1names[0], "A");
    try testing.expectEqualSlices(u8, e1names[1], "B");
    try testing.expect(e2names.len == 1);
    try testing.expectEqualSlices(u8, e2names[0], "A");
    try testing.expect(s1names.len == 1);
    try testing.expectEqualSlices(u8, s1names[0], "a");
    try testing.expect(u1names.len == 2);
    try testing.expectEqualSlices(u8, u1names[0], "a");
    try testing.expectEqualSlices(u8, u1names[1], "b");
}

pub fn fieldTypes(comptime T: type) []const type {
    return switch (@typeInfo(T)) {
        .@"struct" => |s| s.field_types,
        .@"union" => |u| u.field_types,
        else => @compileError("Expected struct or union type, found '" ++ @typeName(T) ++ "'"),
    };
}

test fieldTypes {
    const S1 = struct {
        a: u8,
    };
    const U1 = union {
        a: u8,
        b: void,
    };

    const s1types = comptime fieldTypes(S1);
    const u1types = comptime fieldTypes(U1);

    try testing.expect(s1types.len == 1);
    try testing.expect(s1types[0] == u8);
    try testing.expect(u1types.len == 2);
    try testing.expect(u1types[0] == u8);
    try testing.expect(u1types[1] == void);
}

/// Given an enum or error set type, returns a pointer to an array containing all tags for that
/// enum or error set.
pub fn tags(comptime T: type) *const [fieldNames(T).len]T {
    return comptime blk: {
        const field_names = fieldNames(T);
        var res: [field_names.len]T = undefined;
        for (field_names, 0..) |field_name, i| {
            res[i] = @field(T, field_name);
        }
        const final = res;
        break :blk &final;
    };
}

test tags {
    const E1 = enum { A, B };
    const E2 = error{A};

    const e1_tags = tags(E1);
    const e2_tags = tags(E2);

    try testing.expect(e1_tags.len == 2);
    try testing.expectEqual(E1.A, e1_tags[0]);
    try testing.expectEqual(E1.B, e1_tags[1]);
    try testing.expect(e2_tags.len == 1);
    try testing.expectEqual(E2.A, e2_tags[0]);
}

/// Returns an enum with a variant named after each field of `T`.
pub fn FieldEnum(comptime T: type) type {
    const field_names = fieldNames(T);

    switch (@typeInfo(T)) {
        .@"union" => |@"union"| if (@"union".tag_type) |EnumTag| {
            for (std.enums.values(EnumTag), 0..) |v, i| {
                if (@backingInt(v) != i) break; // enum values not consecutive
                if (!std.mem.eql(u8, @tagName(v), field_names[i])) break; // fields out of order
            } else {
                return EnumTag;
            }
        },
        else => {},
    }

    if (field_names.len == 0) return enum {};
    const IntTag = std.math.IntFittingRange(0, field_names.len - 1);
    return @Enum(IntTag, .exhaustive, field_names, &std.simd.iota(IntTag, field_names.len));
}

fn expectEqualEnum(expected: anytype, actual: @TypeOf(expected)) !void {
    try testing.expectEqual(@typeInfo(expected).@"enum", @typeInfo(actual).@"enum");
    try testing.expectEqual(
        @typeInfo(expected).@"enum".tag_type,
        @typeInfo(actual).@"enum".tag_type,
    );
    // For comparing decls and fields, we cannot use the meta eql function here
    // because the language does not guarantee that the slice pointers for field names
    // and decl names will be the same.
    comptime {
        const expected_field_names = @typeInfo(expected).@"enum".field_names;
        const expected_field_values = @typeInfo(expected).@"enum".field_values;
        const actual_field_names = @typeInfo(actual).@"enum".field_names;
        const actual_field_values = @typeInfo(actual).@"enum".field_values;
        if (expected_field_names.len != actual_field_names.len) return error.FailedTest;
        for (expected_field_names, expected_field_values, 0..) |expected_field_name, expected_field_value, i| {
            const actual_field_name = actual_field_names[i];
            const actual_field_value = actual_field_values[i];
            try testing.expectEqual(expected_field_value, actual_field_value);
            try testing.expectEqualStrings(expected_field_name, actual_field_name);
        }
    }
    comptime {
        const expected_decl_names = @typeInfo(expected).@"enum".decl_names;
        const actual_decl_names = @typeInfo(actual).@"enum".decl_names;
        if (expected_decl_names.len != actual_decl_names.len) return error.FailedTest;
        for (expected_decl_names, 0..) |expected_decl_name, i| {
            const actual_decl_name = actual_decl_names[i];
            try testing.expectEqualStrings(expected_decl_name, actual_decl_name);
        }
    }
    try testing.expectEqual(
        @typeInfo(expected).@"enum".mode,
        @typeInfo(actual).@"enum".mode,
    );
}

test FieldEnum {
    try expectEqualEnum(enum {}, FieldEnum(struct {}));
    try expectEqualEnum(enum { a }, FieldEnum(struct { a: u8 }));
    try expectEqualEnum(enum { a, b, c }, FieldEnum(struct { a: u8, b: void, c: f32 }));
    try expectEqualEnum(enum { a, b, c }, FieldEnum(union { a: u8, b: void, c: f32 }));

    const Tagged = union(enum) { a: u8, b: void, c: f32 };
    try testing.expectEqual(Tag(Tagged), FieldEnum(Tagged));

    const Tag2 = enum { a, b, c };
    const Tagged2 = union(Tag2) { a: u8, b: void, c: f32 };
    try testing.expect(Tag(Tagged2) == FieldEnum(Tagged2));

    const Tag3 = enum(u8) { a, b, c = 7 };
    const Tagged3 = union(Tag3) { a: u8, b: void, c: f32 };
    try testing.expect(Tag(Tagged3) != FieldEnum(Tagged3));
}

pub fn DeclEnum(comptime T: type) type {
    const decl_names = declarations(T);
    if (decl_names.len == 0) return enum {};
    const IntTag = std.math.IntFittingRange(0, decl_names.len - 1);
    return @Enum(IntTag, .exhaustive, decl_names, &std.simd.iota(IntTag, decl_names.len));
}

test DeclEnum {
    const A = struct {
        pub const a: u8 = 0;
    };
    const B = union {
        foo: void,

        pub const a: u8 = 0;
        pub const b: void = {};
        pub const c: f32 = 0;
    };
    const C = enum {
        bar,

        pub const a: u8 = 0;
        pub const b: void = {};
        pub const c: f32 = 0;
    };
    const D = struct {};

    try expectEqualEnum(enum { a }, DeclEnum(A));
    try expectEqualEnum(enum { a, b, c }, DeclEnum(B));
    try expectEqualEnum(enum { a, b, c }, DeclEnum(C));
    try expectEqualEnum(enum {}, DeclEnum(D));
}

pub fn BareUnion(comptime T: type) type {
    const u = switch (@typeInfo(T)) {
        .@"union" => |u| u,
        else => @compileError("expected union type, found '" ++ @typeName(T) ++ "'"),
    };
    return @Union(u.layout, null, u.field_names, u.field_types[0..], u.field_attrs[0..]);
}

/// For enums, packed unions and packed structs, returns their backing integer type.
/// For tagged unions, returns the backing integer type of their enum tag type.
pub fn BackingInt(comptime T: type) type {
    switch (@typeInfo(T)) {
        .@"enum" => |info| return info.tag_type,
        .@"struct" => |info| if (info.backing_integer) |Int| return Int,
        .@"union" => |info| switch (info.layout) {
            .@"packed" => return info.backing_integer.?,
            .auto => if (info.tag_type) |EnumTag|
                return @typeInfo(EnumTag).@"enum".tag_type,
            .@"extern" => {},
        },
        else => {},
    }
    @compileError("expected enum, tagged union, packed union or packed struct type, found '" ++ @typeName(T) ++ "'");
}

test BackingInt {
    const E = enum(u8) { a, b, c };
    try testing.expect(BackingInt(E) == u8);

    const S = packed struct(u16) { x: u8, y: i8 };
    try testing.expect(BackingInt(S) == u16);

    const U = packed union(i32) { a: u32, b: enum(i32) { _ } };
    try testing.expect(BackingInt(U) == i32);

    const T = union(enum(i8)) { a, b, c };
    try testing.expect(BackingInt(T) == i8);
}

pub fn Tag(comptime T: type) type {
    return switch (@typeInfo(T)) {
        .@"enum" => |info| info.tag_type,
        .@"union" => |info| info.tag_type orelse @compileError(@typeName(T) ++ " has no tag type"),
        else => @compileError("expected enum or union type, found '" ++ @typeName(T) ++ "'"),
    };
}

test Tag {
    const E = enum(u8) {
        C = 33,
        D,
    };
    const U = union(E) {
        C: u8,
        D: u16,
    };

    try testing.expect(Tag(E) == u8);
    try testing.expect(Tag(U) == E);
}

/// Returns the active tag of a tagged union
pub fn activeTag(u: anytype) Tag(@TypeOf(u)) {
    const T = @TypeOf(u);
    return @as(Tag(T), u);
}

test activeTag {
    const UE = enum {
        Int,
        Float,
    };

    const U = union(UE) {
        Int: u32,
        Float: f32,
    };

    var u = U{ .Int = 32 };
    try testing.expect(activeTag(u) == UE.Int);

    u = U{ .Float = 112.9876 };
    try testing.expect(activeTag(u) == UE.Float);
}

/// Compares two of any type for equality. Containers that do not support comparison
/// on their own are compared on a field-by-field basis. Pointers are not followed.
pub fn eql(a: anytype, b: @TypeOf(a)) bool {
    const T = @TypeOf(a);

    switch (@typeInfo(T)) {
        .@"struct" => |info| {
            if (info.layout == .@"packed") return a == b;

            inline for (info.field_names) |field_name| {
                if (!eql(@field(a, field_name), @field(b, field_name))) return false;
            }
            return true;
        },
        .error_union => {
            if (a) |a_p| {
                if (b) |b_p| return eql(a_p, b_p) else |_| return false;
            } else |a_e| {
                if (b) |_| return false else |b_e| return a_e == b_e;
            }
        },
        .@"union" => |info| {
            if (info.layout == .@"packed") return a == b;
            const UnionTag = info.tag_type orelse
                @compileError("cannot compare untagged union type " ++ @typeName(T));

            const tag_a: UnionTag = a;
            const tag_b: UnionTag = b;
            if (tag_a != tag_b) return false;

            return switch (a) {
                inline else => |val, tag| return eql(val, @field(b, @tagName(tag))),
            };
        },
        .array => {
            for (a, b) |x, y| {
                if (!eql(x, y)) return false;
            }
            return true;
        },
        .vector => return @reduce(.And, a == b),
        .pointer => |info| {
            return switch (info.size) {
                .one, .many, .c => a == b,
                .slice => a.ptr == b.ptr and a.len == b.len,
            };
        },
        .optional => {
            const some_a = a orelse return b == null;
            const some_b = b orelse return false;
            return eql(some_a, some_b);
        },
        else => return a == b,
    }
}

test eql {
    const S = struct {
        a: u32,
        b: f64,
        c: [5]u8,
    };

    const U = union(enum) {
        s: S,
        f: ?f32,
    };

    const s_1 = S{
        .a = 134,
        .b = 123.3,
        .c = "12345".*,
    };

    var s_3 = S{
        .a = 134,
        .b = 123.3,
        .c = "12345".*,
    };

    const u_1 = U{ .f = 24 };
    const u_2 = U{ .s = s_1 };
    const u_3 = U{ .f = 24 };

    try testing.expect(eql(s_1, s_3));
    try testing.expect(eql(&s_1, &s_1));
    try testing.expect(!eql(&s_1, &s_3));
    try testing.expect(eql(u_1, u_3));
    try testing.expect(!eql(u_1, u_2));

    const a1 = "abcdef".*;
    const a2 = "abcdef".*;
    const a3 = "ghijkl".*;

    try testing.expect(eql(a1, a2));
    try testing.expect(!eql(a1, a3));

    const EU = struct {
        fn tst(err: bool) !u8 {
            if (err) return error.Error;
            return @as(u8, 5);
        }
    };

    try testing.expect(eql(EU.tst(true), EU.tst(true)));
    try testing.expect(eql(EU.tst(false), EU.tst(false)));
    try testing.expect(!eql(EU.tst(false), EU.tst(true)));

    const CU = union(enum) {
        a: void,
        b: void,
        c: comptime_int,
    };

    try testing.expect(eql(CU{ .a = {} }, .a));
    try testing.expect(!eql(CU{ .a = {} }, .b));

    if (builtin.cpu.arch == .hexagon) return error.SkipZigTest;

    const V = @Vector(4, u32);
    const v1: V = @splat(1);
    const v2: V = @splat(1);
    const v3: V = @splat(2);

    try testing.expect(eql(v1, v2));
    try testing.expect(!eql(v1, v3));
}

/// Given a type and a name, return the field index according to source order.
/// Returns `null` if the field is not found.
pub fn fieldIndex(comptime T: type, comptime name: []const u8) ?comptime_int {
    inline for (fieldNames(T), 0..) |field_name, i| {
        if (mem.eql(u8, field_name, name))
            return i;
    }
    return null;
}

pub fn Float(comptime bit_count: u8) type {
    return switch (bit_count) {
        16 => f16,
        32 => f32,
        64 => f64,
        80 => f80,
        128 => f128,
        else => @compileError("invalid float bit count"),
    };
}
test Float {
    try testing.expectEqual(f16, Float(16));
    try testing.expectEqual(f32, Float(32));
    try testing.expectEqual(f64, Float(64));
    try testing.expectEqual(f80, Float(80));
    try testing.expectEqual(f128, Float(128));
}

/// For a given function type, returns a tuple type which fields will
/// correspond to the argument types.
///
/// Examples:
/// - `ArgsTuple(fn () void)` ⇒ `tuple { }`
/// - `ArgsTuple(fn (a: u32) u32)` ⇒ `tuple { u32 }`
/// - `ArgsTuple(fn (a: u32, b: f16) noreturn)` ⇒ `tuple { u32, f16 }`
pub fn ArgsTuple(comptime Function: type) type {
    const info = @typeInfo(Function);
    if (info != .@"fn")
        @compileError("ArgsTuple expects a function type");

    const function_info = info.@"fn";
    if (function_info.attrs.varargs)
        @compileError("Cannot create ArgsTuple for variadic function");

    var argument_field_list: [function_info.param_types.len]type = undefined;
    inline for (function_info.param_types, 0..) |arg_type, i| {
        const T = arg_type orelse @compileError("cannot create ArgsTuple for function with an 'anytype' parameter");
        argument_field_list[i] = T;
    }

    return @Tuple(&argument_field_list);
}

const TupleTester = struct {
    fn assertTypeEqual(comptime Expected: type, comptime Actual: type) void {
        if (Expected != Actual)
            @compileError("Expected type " ++ @typeName(Expected) ++ ", but got type " ++ @typeName(Actual));
    }

    fn assertTuple(comptime expected: anytype, comptime Actual: type) void {
        const info = @typeInfo(Actual);
        if (info != .@"struct")
            @compileError("Expected struct type");
        if (!info.@"struct".is_tuple)
            @compileError("Struct type must be a tuple type");

        const field_names = info.@"struct".field_names;
        if (expected.len != field_names.len) {
            const msg = std.fmt.comptimePrint("Argument count mismatch: expected {d}, got {d}", .{ expected.len, field_names.len });
            @compileError(msg);
        }

        inline for (field_names, info.@"struct".field_types, 0..) |fld_name, fld_type, i| {
            if (expected[i] != fld_type) {
                @compileError("Field " ++ fld_name ++ " expected to be type " ++ @typeName(expected[i]) ++ ", but was type " ++ @typeName(fld_type));
            }
        }
    }
};

test ArgsTuple {
    TupleTester.assertTuple(.{}, ArgsTuple(fn () void));
    TupleTester.assertTuple(.{u32}, ArgsTuple(fn (a: u32) []const u8));
    TupleTester.assertTuple(.{ u32, f16 }, ArgsTuple(fn (a: u32, b: f16) noreturn));
    TupleTester.assertTuple(.{ u32, f16, []const u8, void }, ArgsTuple(fn (a: u32, b: f16, c: []const u8, void) noreturn));
    TupleTester.assertTuple(.{u32}, ArgsTuple(fn (comptime a: u32) []const u8));
}

test "ArgsTuple forwarding" {
    const T1 = @Tuple(&.{ u32, f32, i8 });
    const T2 = std.meta.ArgsTuple(fn (u32, f32, i8) void);
    const T3 = std.meta.ArgsTuple(fn (u32, f32, i8) callconv(.c) noreturn);

    if (T1 != T2) {
        @compileError("std.meta.ArgsTuple produces different types than @Tuple");
    }
    if (T1 != T3) {
        @compileError("std.meta.ArgsTuple produces different types for the same argument lists.");
    }
}

/// Returns whether `error_union` contains an error.
pub fn isError(error_union: anytype) bool {
    return if (error_union) |_| false else |_| true;
}

test isError {
    try std.testing.expect(isError(math.divTrunc(u8, 5, 0)));
    try std.testing.expect(!isError(math.divTrunc(u8, 5, 5)));
}

/// Returns true if a type has a namespace and the namespace contains `name`;
/// `false` otherwise. Result is always comptime-known.
pub inline fn hasFn(comptime T: type, comptime name: []const u8) bool {
    switch (@typeInfo(T)) {
        .@"struct", .@"union", .@"enum", .@"opaque" => {},
        else => return false,
    }
    if (!@hasDecl(T, name))
        return false;

    return @typeInfo(@TypeOf(@field(T, name))) == .@"fn";
}

test hasFn {
    const S1 = struct {
        pub fn foo() void {}
    };

    try std.testing.expect(hasFn(S1, "foo"));
    try std.testing.expect(!hasFn(S1, "bar"));
    try std.testing.expect(!hasFn(*S1, "foo"));

    const S2 = struct {
        foo: fn () void,
    };

    try std.testing.expect(!hasFn(S2, "foo"));
}

/// Returns true if a type has a `name` method; `false` otherwise.
/// Result is always comptime-known.
pub inline fn hasMethod(comptime T: type, comptime name: []const u8) bool {
    return switch (@typeInfo(T)) {
        .pointer => |P| switch (P.size) {
            .one => hasFn(P.child, name),
            .many, .slice, .c => false,
        },
        else => hasFn(T, name),
    };
}

test hasMethod {
    try std.testing.expect(!hasMethod(u32, "foo"));
    try std.testing.expect(!hasMethod([]u32, "len"));
    try std.testing.expect(!hasMethod(struct { u32, u64 }, "len"));

    const S1 = struct {
        pub fn foo() void {}
    };

    try std.testing.expect(hasMethod(S1, "foo"));
    try std.testing.expect(hasMethod(*S1, "foo"));

    try std.testing.expect(!hasMethod(S1, "bar"));
    try std.testing.expect(!hasMethod(*[1]S1, "foo"));
    try std.testing.expect(!hasMethod(*[10]S1, "foo"));
    try std.testing.expect(!hasMethod([]S1, "foo"));

    const S2 = struct {
        foo: fn () void,
    };

    try std.testing.expect(!hasMethod(S2, "foo"));

    const U = union {
        pub fn foo() void {}
    };

    try std.testing.expect(hasMethod(U, "foo"));
    try std.testing.expect(hasMethod(*U, "foo"));
    try std.testing.expect(!hasMethod(U, "bar"));
}

/// True if every value of the type `T` has a unique bit pattern representing it.
/// In other words, `T` has no unused bits and no padding.
/// Result is always comptime-known.
pub inline fn hasUniqueRepresentation(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        else => false, // TODO can we know if it's true for some of these types ?

        .@"anyframe",
        .error_set,
        .@"fn",
        => true,

        .bool => false,

        .@"enum" => |info| hasUniqueRepresentation(info.tag_type),
        .int => |info| @sizeOf(T) * 8 == info.bits,

        .pointer => |info| info.size != .slice,

        .optional => |info| switch (@typeInfo(info.child)) {
            .pointer => |ptr| !ptr.attrs.@"allowzero" and switch (ptr.size) {
                .slice, .c => false,
                .one, .many => true,
            },
            else => false,
        },

        .array => |info| hasUniqueRepresentation(info.child),

        .@"struct" => |info| {
            if (info.layout == .@"packed") return @sizeOf(T) * 8 == @bitSizeOf(T);

            var sum_size = @as(usize, 0);

            inline for (info.field_attrs, info.field_types) |field_attr, field_type| {
                if (field_attr.@"comptime") continue;
                if (!hasUniqueRepresentation(field_type)) return false;
                sum_size += @sizeOf(field_type);
            }

            return @sizeOf(T) == sum_size;
        },

        .@"union" => |info| {
            if (info.layout == .@"packed") return @sizeOf(T) * 8 == @bitSizeOf(T);
            inline for (info.field_types) |field_type| {
                if (@sizeOf(field_type) != @sizeOf(T)) return false;
                if (!hasUniqueRepresentation(field_type)) return false;
            }
            return true;
        },

        .vector => |info| hasUniqueRepresentation(info.child) and
            @sizeOf(T) == @sizeOf(info.child) * info.len,
    };
}

test hasUniqueRepresentation {
    const TestStruct1 = struct {
        a: u32,
        b: u32,
    };

    try testing.expect(hasUniqueRepresentation(TestStruct1));

    const TestStruct2 = struct {
        a: u32,
        b: u16,
    };

    try testing.expect(!hasUniqueRepresentation(TestStruct2));

    const TestStruct3 = struct {
        a: u32,
        b: u32,
    };

    try testing.expect(hasUniqueRepresentation(TestStruct3));

    const TestStruct4 = struct { a: []const u8 };

    try testing.expect(!hasUniqueRepresentation(TestStruct4));

    const TestStruct5 = struct { a: TestStruct4 };

    try testing.expect(!hasUniqueRepresentation(TestStruct5));

    const TestStruct6 = packed struct(u8) {
        @"0": bool,
        @"1": bool,
        @"2": bool,
        @"3": bool,
        @"4": bool,
        @"5": bool,
        @"6": bool,
        @"7": bool,
    };

    try testing.expect(hasUniqueRepresentation(TestStruct6));

    const TestUnion2 = extern union {
        a: u32,
        b: u16,
    };

    try testing.expect(!hasUniqueRepresentation(TestUnion2));

    const TestUnion3 = union {
        a: u32,
        b: u16,
    };

    try testing.expect(!hasUniqueRepresentation(TestUnion3));

    const TestUnion4 = union(enum) {
        a: u32,
        b: u16,
    };

    try testing.expect(!hasUniqueRepresentation(TestUnion4));

    const TestUnion5 = extern union {
        a: u32,
        b: i32,
    };

    try testing.expect(hasUniqueRepresentation(TestUnion5));

    const TestUnion6 = packed union(u7) {
        a: u7,
        b: i7,
    };

    try testing.expect(!hasUniqueRepresentation(TestUnion6));

    const TestUnion7 = packed union(u8) {
        a: u8,
        b: i8,
    };

    try testing.expect(hasUniqueRepresentation(TestUnion7));

    inline for ([_]type{ u8, i16, u32, i64 }) |T| {
        try testing.expect(hasUniqueRepresentation(T));
        try testing.expect(hasUniqueRepresentation(enum(T) { _ }));
    }
    inline for ([_]type{ i1, u9, i17, u33, i24 }) |T| {
        try testing.expect(!hasUniqueRepresentation(T));
        try testing.expect(!hasUniqueRepresentation(enum(T) { _ }));
    }

    try testing.expect(hasUniqueRepresentation(*u8));
    try testing.expect(hasUniqueRepresentation(*const u8));
    try testing.expect(hasUniqueRepresentation(?*u8));
    try testing.expect(hasUniqueRepresentation(?*const u8));

    try testing.expect(!hasUniqueRepresentation([]u8));
    try testing.expect(!hasUniqueRepresentation([]const u8));
    try testing.expect(!hasUniqueRepresentation(?[]u8));
    try testing.expect(!hasUniqueRepresentation(?[]const u8));

    try testing.expect(hasUniqueRepresentation(@Vector(std.simd.suggestVectorLength(u8) orelse 1, u8)));
    try testing.expect(@sizeOf(@Vector(3, u8)) == 3 or !hasUniqueRepresentation(@Vector(3, u8)));

    const StructWithComptimeFields = struct {
        comptime should_be_ignored: u64 = 42,
        comptime should_also_be_ignored: [*:0]const u8 = "hope you're having a good day :)",
        field: u32,
    };

    try testing.expect(hasUniqueRepresentation(StructWithComptimeFields));
}
