//! Since the `std.meta.trait` module was removed from Zig's standard //! library, we're re-introducing some of the functions we needed. const std = @import("std"); /// Returns if the provided value is either a `null` /// constant or an optional type with a `null` value. pub fn isNull(value: anytype) bool { return switch (@typeInfo(@TypeOf(value))) { .Null => true, .Optional => value == null, else => false, }; } /// Returns if the provided type is a tuple. pub fn isTuple(comptime T: type) bool { return @typeInfo(T) == .Struct and @typeInfo(T).Struct.is_tuple; } /// Returns true if the passed type will coerce to []const u8. /// Any of the following are considered strings: /// ``` /// []const u8, [:S]const u8, *const [N]u8, *const [N:S]u8, /// []u8, [:S]u8, *[:S]u8, *[N:S]u8. /// ``` /// These types are not considered strings: /// ``` /// u8, [N]u8, [*]const u8, [*:0]const u8, /// [*]const [N]u8, []const u16, []const i8, /// *const u8, ?[]const u8, ?*const [N]u8. /// ``` pub fn isZigString(comptime T: type) bool { return comptime blk: { // Only pointer types can be strings, no optionals const info = @typeInfo(T); if (info != .Pointer) break :blk false; const ptr = &info.Pointer; // Check for CV qualifiers that would prevent coerction to []const u8 if (ptr.is_volatile or ptr.is_allowzero) break :blk false; // If it's already a slice, simple check. if (ptr.size == .Slice) { break :blk ptr.child == u8; } // Otherwise check if it's an array type that coerces to slice. if (ptr.size == .One) { const child = @typeInfo(ptr.child); if (child == .Array) { const arr = &child.Array; break :blk arr.child == u8; } } break :blk false; }; } /// Returns the type of the specified expression, which must be either /// just a `type`, or a tuple in the form of `.{ TRelation, TTarget }`, /// representing a relationship pair in Flecs. /// /// If the expression is a pair, the type is determined like this: /// - If `TRelation` is a non-zero-sized type, it is returned. /// - If `TTarget` is a non-zero-sized type, it is returned. /// - Otherwise, a compile error is raised. pub fn AnyToType(comptime expr: anytype) type { switch (@typeInfo(@TypeOf(expr))) { .Type => return expr, .Struct => |s| { if (!s.is_tuple or s.fields.len != 2) @compileError("Expression must be a type or a tuple of two types"); const TRelation = expr[0]; const TTarget = expr[1]; if (@TypeOf(TRelation) != type) @compileError("TRelation must be a type, but is " ++ @typeName(@TypeOf(TRelation))); if (@TypeOf(TTarget) != type) @compileError("TTarget must be a type, but is " ++ @typeName(@TypeOf(TTarget))); if (@sizeOf(TRelation) > 0) return TRelation; if (@sizeOf(TTarget) > 0) return TTarget; @compileError("Either TRelation or TTarget must be a non-zero-sized type"); }, else => @compileError("Expression must be a type or a tuple of two types"), } } /// Gets a Flecs-compatible symbol name for the specified type. /// Supports structs (except anonymous or generic), as well as /// single-item pointers to supported structs. /// /// If the struct defines a public accessible `name`, it is used. pub fn flecsTypeName(comptime T: type) [:0]const u8 { const allowed_characters = "abcdefghijklmnopqrstuvwxyz" ++ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ++ "0123456789_"; switch (@typeInfo(T)) { .Struct => { comptime var name: [:0]const u8 = undefined; comptime if (@hasDecl(T, "name")) { name = @field(T, "name"); } else { name = @typeName(T); if (std.mem.indexOfScalar(u8, name, '{') != null) @compileError("Expected type with simple name, got '" ++ @typeName(T) ++ "' (anonymous type?)"); // If name includes fully qualified namespace, strip it. if (std.mem.lastIndexOfScalar(u8, name, '.')) |i| name = name[(i + 1)..]; if (std.mem.indexOfScalar(u8, name, '(') != null) @compileError("Expected type with simple name, got '" ++ @typeName(T) ++ "' (generic type?)"); if (std.mem.indexOf(u8, name, "__") != null) @compileError("Expected type with simple name, got '" ++ @typeName(T) ++ "' (anonymous type?)"); if (std.mem.indexOfNone(u8, name, allowed_characters) != null) @compileError("Expected type with simple name, got '" ++ @typeName(T) ++ "' (invalid characters?)"); }; return name; }, .Pointer => |p| { if (p.size != .One) @compileError("Expected struct or single-item pointer, got '" ++ @typeName(T) ++ "'"); return flecsTypeName(p.child); }, else => @compileError("Expected struct or single-item pointer, got '" ++ @typeName(T) ++ "'"), } } const expect = @import("./test/expect.zig"); test flecsTypeName { const Foo = struct {}; try expect.equal("Foo", flecsTypeName(Foo)); try expect.equal("Foo", flecsTypeName(*Foo)); const Bar = struct { const Baz = struct { const Quux = struct {}; }; }; try expect.equal("Quux", flecsTypeName(Bar.Baz.Quux)); try expect.equal("Anon", flecsTypeName((struct { const Anon = struct {}; }).Anon)); try expect.equal("Random", flecsTypeName(std.Random)); // error: Expected struct or single-item pointer, got 'i32' // _ = flecsTypeName(i32); // error: Expected type with simple name, got 'array_list.ArrayListAligned(i32,null)' (generic type?) // _ flecsTypeName(std.ArrayList(i32)); // error: Expected type with simple name, got 'meta.decltest.flecsTypeName__struct_...' (anonymous type?) // _ = flecsTypeName(struct {}); // error: Expected type with simple name, got 'struct{comptime foo: comptime_int = 0}' (anonymous type?) // _ = flecsTypeName(@TypeOf(.{ .foo = 0 })); // error: Expected type with simple name, got 'meta.decltest.flecsTypeName.%&!*' (invalid characters?) // const @"%&!*" = struct {}; // _ = flecsTypeName(@"%&!*"); }