Files
Andrew Kelley 8dfddf95fe std.meta: deprecate fieldInfo, fieldNames, fieldTypes
Technically there is one valid use case for `fieldNames` which is use
with an enum or union so that those types can be used interchangeably.
But in practice these functions are mainly abused, because the callsites
always know what kind of type it is.

This commit encourages Zig users to embrace using `@typeInfo` directly
when doing type reflection.
2026-08-08 19:15:10 +02:00

1130 lines
35 KiB
Zig

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));
}