const builtin = @import("builtin"); const std = @import("std.zig"); const assert = std.debug.assert; const mem = std.mem; const testing = std.testing; 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"); } /// Returns the list of declaration names of namespace types. /// /// This function is only useful when the callsite does not know statically /// which kind of container 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 => comptime unreachable, // type lacks namespace }; } 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 in favor of @hasDecl"); /// To be removed after Zig 0.17.0 is tagged. pub const fields = @compileError("deprecated in favor of @typeInfo"); /// Deprecated in favor of `@typeInfo`. /// /// To be removed after 0.17.0 is tagged. 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); } /// Deprecated in favor of `@typeInfo`. /// /// To be removed after 0.17.0 is tagged. 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 => comptime unreachable, }; } 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"); } /// Deprecated in favor of `@typeInfo`. /// /// To be removed after 0.17.0 is tagged. pub fn fieldTypes(comptime T: type) []const type { return switch (@typeInfo(T)) { .@"struct" => |s| s.field_types, .@"union" => |u| u.field_types, else => comptime unreachable, }; } 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(std.math.divTrunc(u8, 5, 0))); try std.testing.expect(!isError(std.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)); } /// Given a pointer type, type-erases the array length if present, returning an /// equivalent pointer type that is always a slice. pub fn Slice(comptime Pointer: type) type { const info = @typeInfo(Pointer).pointer; switch (info.size) { .slice => return Pointer, .one => { const child_info = @typeInfo(info.child); comptime assert(child_info == .array); const sentinel_ptr: ?*const child_info.array.child = @ptrCast(@alignCast(child_info.array.sentinel_ptr)); return @Pointer( .slice, info.attrs, child_info.array.child, if (sentinel_ptr) |ptr| ptr.* else null, ); }, else => unreachable, } } /// Given a pointer type, removes the sentinel if present, returning an /// equivalent pointer type with no sentinel pub fn AbsorbSentinel(comptime Pointer: type) type { const info = @typeInfo(Pointer).pointer; switch (info.size) { .slice => return @Pointer(.slice, info.attrs, info.child, null), .one => { const child_info = @typeInfo(info.child).array; if (child_info.sentinel_ptr == null) { return Pointer; } else { return @Pointer(.one, info.attrs, [child_info.len + 1]child_info.child, null); } }, else => unreachable, } } test Slice { try testing.expectEqual([]i32, Slice(*[10]i32)); } test AbsorbSentinel { try testing.expectEqual(*[5]u32, AbsorbSentinel(*[4:0]u32)); }