From 2a3fdd52ce7bd0cc577947e49bc8a3e69a34b6d0 Mon Sep 17 00:00:00 2001 From: tgschultz Date: Fri, 19 Oct 2018 16:19:22 -0500 Subject: [PATCH] Add std.meta (#1662) Implement std.meta --- std/index.zig | 2 + std/mem.zig | 179 ++++++++++++++++ std/meta/index.zig | 525 +++++++++++++++++++++++++++++++++++++++++++++ std/meta/trait.zig | 449 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1155 insertions(+) create mode 100644 std/meta/index.zig create mode 100644 std/meta/trait.zig diff --git a/std/index.zig b/std/index.zig index 18489969df..55ad016bb1 100644 --- a/std/index.zig +++ b/std/index.zig @@ -32,6 +32,7 @@ pub const io = @import("io.zig"); pub const json = @import("json.zig"); pub const macho = @import("macho.zig"); pub const math = @import("math/index.zig"); +pub const meta = @import("meta/index.zig"); pub const mem = @import("mem.zig"); pub const net = @import("net.zig"); pub const os = @import("os/index.zig"); @@ -74,6 +75,7 @@ test "std" { _ = @import("json.zig"); _ = @import("macho.zig"); _ = @import("math/index.zig"); + _ = @import("meta/index.zig"); _ = @import("mem.zig"); _ = @import("net.zig"); _ = @import("heap.zig"); diff --git a/std/mem.zig b/std/mem.zig index 908d54e02b..ca93e0d2d1 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -4,6 +4,8 @@ const assert = debug.assert; const math = std.math; const builtin = @import("builtin"); const mem = @This(); +const meta = std.meta; +const trait = meta.trait; pub const Allocator = struct.{ pub const Error = error.{OutOfMemory}; @@ -863,3 +865,180 @@ pub fn endianSwap(comptime T: type, x: T) T { test "std.mem.endianSwap" { assert(endianSwap(u32, 0xDEADBEEF) == 0xEFBEADDE); } + + + +fn AsBytesReturnType(comptime P: type) type +{ + if(comptime !trait.isSingleItemPtr(P)) @compileError("expected single item " + ++ "pointer, passed " ++ @typeName(P)); + + const size = usize(@sizeOf(meta.Child(P))); + const alignment = comptime meta.alignment(P); + if(comptime trait.isConstPtr(P)) return *align(alignment) const [size]u8; + return *align(alignment) [size]u8; +} + +///Given a pointer to a single item, returns a slice of the underlying bytes, preserving constness. +pub fn asBytes(ptr: var) AsBytesReturnType(@typeOf(ptr)) +{ + const P = @typeOf(ptr); + return @ptrCast(AsBytesReturnType(P), ptr); +} + +test "std.mem.asBytes" +{ + const deadbeef = u32(0xDEADBEEF); + const deadbeef_bytes = switch(builtin.endian) + { + builtin.Endian.Big => "\xDE\xAD\xBE\xEF", + builtin.Endian.Little => "\xEF\xBE\xAD\xDE", + }; + + debug.assert(std.mem.eql(u8, asBytes(&deadbeef), deadbeef_bytes)); + + var codeface = u32(0xC0DEFACE); + for(asBytes(&codeface).*) |*b| b.* = 0; + debug.assert(codeface == 0); + + const S = packed struct. + { + a: u8, + b: u8, + c: u8, + d: u8, + }; + + const inst = S.{ .a = 0xBE, .b = 0xEF, .c = 0xDE, .d = 0xA1, }; + debug.assert(std.mem.eql(u8, asBytes(&inst), "\xBE\xEF\xDE\xA1")); +} + +///Given any value, returns a copy of its bytes in an array. +pub fn toBytes(value: var) [@sizeOf(@typeOf(value))]u8 +{ + return asBytes(&value).*; +} + +test "std.mem.toBytes" +{ + var my_bytes = toBytes(u32(0x12345678)); + switch(builtin.endian) + { + builtin.Endian.Big => debug.assert(std.mem.eql(u8, my_bytes, "\x12\x34\x56\x78")), + builtin.Endian.Little => debug.assert(std.mem.eql(u8, my_bytes, "\x78\x56\x34\x12")), + } + + my_bytes[0] = '\x99'; + switch(builtin.endian) + { + builtin.Endian.Big => debug.assert(std.mem.eql(u8, my_bytes, "\x99\x34\x56\x78")), + builtin.Endian.Little => debug.assert(std.mem.eql(u8, my_bytes, "\x99\x56\x34\x12")), + } +} + + +fn BytesAsValueReturnType(comptime T: type, comptime B: type) type +{ + const size = usize(@sizeOf(T)); + + if(comptime !trait.is(builtin.TypeId.Pointer)(B) or meta.Child(B) != [size]u8) + { + @compileError("expected *[N]u8 " ++ ", passed " ++ @typeName(B)); + } + + const alignment = comptime meta.alignment(B); + + return if(comptime trait.isConstPtr(B)) *align(alignment) const T else *align(alignment) T; +} + +///Given a pointer to an array of bytes, returns a pointer to a value of the specified type +/// backed by those bytes, preserving constness. +pub fn bytesAsValue(comptime T: type, bytes: var) BytesAsValueReturnType(T, @typeOf(bytes)) +{ + return @ptrCast(BytesAsValueReturnType(T, @typeOf(bytes)), bytes); +} + +test "std.mem.bytesAsValue" +{ + const deadbeef = u32(0xDEADBEEF); + const deadbeef_bytes = switch(builtin.endian) + { + builtin.Endian.Big => "\xDE\xAD\xBE\xEF", + builtin.Endian.Little => "\xEF\xBE\xAD\xDE", + }; + + debug.assert(deadbeef == bytesAsValue(u32, &deadbeef_bytes).*); + + var codeface_bytes = switch(builtin.endian) + { + builtin.Endian.Big => "\xC0\xDE\xFA\xCE", + builtin.Endian.Little => "\xCE\xFA\xDE\xC0", + }; + var codeface = bytesAsValue(u32, &codeface_bytes); + debug.assert(codeface.* == 0xC0DEFACE); + codeface.* = 0; + for(codeface_bytes) |b| debug.assert(b == 0); + + const S = packed struct. + { + a: u8, + b: u8, + c: u8, + d: u8, + }; + + const inst = S.{ .a = 0xBE, .b = 0xEF, .c = 0xDE, .d = 0xA1, }; + const inst_bytes = "\xBE\xEF\xDE\xA1"; + const inst2 = bytesAsValue(S, &inst_bytes); + debug.assert(meta.eql(inst, inst2.*)); +} + +///Given a pointer to an array of bytes, returns a value of the specified type backed by a +/// copy of those bytes. +pub fn bytesToValue(comptime T: type, bytes: var) T +{ + return bytesAsValue(T, &bytes).*; +} + test "std.mem.bytesToValue" +{ + const deadbeef_bytes = switch(builtin.endian) + { + builtin.Endian.Big => "\xDE\xAD\xBE\xEF", + builtin.Endian.Little => "\xEF\xBE\xAD\xDE", + }; + + const deadbeef = bytesToValue(u32, deadbeef_bytes); + debug.assert(deadbeef == u32(0xDEADBEEF)); +} + + +fn SubArrayPtrReturnType(comptime T: type, comptime length: usize) type +{ + if(trait.isConstPtr(T)) return *const [length]meta.Child(meta.Child(T)); + return *[length]meta.Child(meta.Child(T)); +} + +///Given a pointer to an array, returns a pointer to a portion of that array, preserving constness. +pub fn subArrayPtr(ptr: var, comptime start: usize, comptime length: usize) + SubArrayPtrReturnType(@typeOf(ptr), length) +{ + debug.assert(start + length <= ptr.*.len); + + const ReturnType = SubArrayPtrReturnType(@typeOf(ptr), length); + const T = meta.Child(meta.Child(@typeOf(ptr))); + return @ptrCast(ReturnType, &ptr[start]); +} + +test "std.mem.subArrayPtr" +{ + const a1 = "abcdef"; + const sub1 = subArrayPtr(&a1, 2, 3); + debug.assert(std.mem.eql(u8, sub1.*, "cde")); + + var a2 = "abcdef"; + var sub2 = subArrayPtr(&a2, 2, 3); + + debug.assert(std.mem.eql(u8, sub2, "cde")); + sub2[1] = 'X'; + debug.assert(std.mem.eql(u8, a2, "abcXef")); +} \ No newline at end of file diff --git a/std/meta/index.zig b/std/meta/index.zig new file mode 100644 index 0000000000..5bbbc1f9ae --- /dev/null +++ b/std/meta/index.zig @@ -0,0 +1,525 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const debug = std.debug; +const mem = std.mem; +const math = std.math; + +pub const trait = @import("trait.zig"); + +const TypeId = builtin.TypeId; +const TypeInfo = builtin.TypeInfo; + +pub fn tagName(v: var) []const u8 { + const T = @typeOf(v); + switch (@typeInfo(T)) { + TypeId.Enum => |info| { + const Tag = info.tag_type; + inline for (info.fields) |field| { + if (field.value == @enumToInt(v)) return field.name; + } + + unreachable; + }, + TypeId.Union => |info| { + const UnionTag = if(info.tag_type) |UT| UT else @compileError("union is untagged"); + const Tag = @typeInfo(UnionTag).Enum.tag_type; + inline for (info.fields) |field| { + if (field.enum_field.?.value == @enumToInt(UnionTag(v))) + return field.name; + } + + unreachable; + }, + TypeId.ErrorSet => |info| { + inline for (info.errors) |err| { + if (err.value == @errorToInt(v)) return err.name; + } + + unreachable; + }, + else => @compileError("expected enum, error set or union type, found '" + ++ @typeName(T) ++ "'"), + } +} + +test "std.meta.tagName" { + const E1 = enum.{ + A, + B, + }; + const E2 = enum(u8).{ + C = 33, + D, + }; + const U1 = union(enum).{ + G: u8, + H: u16, + }; + const U2 = union(E2).{ + C: u8, + D: u16, + }; + + var u1g = U1.{ .G = 0 }; + var u1h = U1.{ .H = 0 }; + var u2a = U2.{ .C = 0 }; + var u2b = U2.{ .D = 0 }; + + debug.assert(mem.eql(u8, tagName(E1.A), "A")); + debug.assert(mem.eql(u8, tagName(E1.B), "B")); + debug.assert(mem.eql(u8, tagName(E2.C), "C")); + debug.assert(mem.eql(u8, tagName(E2.D), "D")); + debug.assert(mem.eql(u8, tagName(error.E), "E")); + debug.assert(mem.eql(u8, tagName(error.F), "F")); + debug.assert(mem.eql(u8, tagName(u1g), "G")); + debug.assert(mem.eql(u8, tagName(u1h), "H")); + debug.assert(mem.eql(u8, tagName(u2a), "C")); + debug.assert(mem.eql(u8, tagName(u2b), "D")); +} + +pub fn bitCount(comptime T: type) u32 { + return switch (@typeInfo(T)) { + TypeId.Int => |info| info.bits, + TypeId.Float => |info| info.bits, + else => @compileError("Expected int or float type, found '" ++ @typeName(T) ++ "'"), + }; +} + +test "std.meta.bitCount" { + debug.assert(bitCount(u8) == 8); + debug.assert(bitCount(f32) == 32); +} + +pub fn alignment(comptime T: type) u29 { + //@alignOf works on non-pointer types + const P = if(comptime trait.is(TypeId.Pointer)(T)) T else *T; + return @typeInfo(P).Pointer.alignment; +} + +test "std.meta.alignment" { + debug.assert(alignment(u8) == 1); + debug.assert(alignment(*align(1) u8) == 1); + debug.assert(alignment(*align(2) u8) == 2); + debug.assert(alignment([]align(1) u8) == 1); + debug.assert(alignment([]align(2) u8) == 2); +} + +pub fn Child(comptime T: type) type { + return switch (@typeInfo(T)) { + TypeId.Array => |info| info.child, + TypeId.Pointer => |info| info.child, + TypeId.Optional => |info| info.child, + TypeId.Promise => |info| if(info.child) |child| child else null, + else => @compileError("Expected promise, pointer, optional, or array type, " + ++ "found '" ++ @typeName(T) ++ "'"), + }; +} + +test "std.meta.Child" { + debug.assert(Child([1]u8) == u8); + debug.assert(Child(*u8) == u8); + debug.assert(Child([]u8) == u8); + debug.assert(Child(?u8) == u8); + debug.assert(Child(promise->u8) == u8); +} + +pub fn containerLayout(comptime T: type) TypeInfo.ContainerLayout { + return switch (@typeInfo(T)) { + TypeId.Struct => |info| info.layout, + TypeId.Enum => |info| info.layout, + TypeId.Union => |info| info.layout, + else => @compileError("Expected struct, enum or union type, found '" + ++ @typeName(T) ++ "'"), + }; +} + +test "std.meta.containerLayout" { + const E1 = enum.{ + A, + }; + const E2 = packed enum.{ + A, + }; + const E3 = extern enum.{ + A, + }; + 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, + }; + + debug.assert(containerLayout(E1) == TypeInfo.ContainerLayout.Auto); + debug.assert(containerLayout(E2) == TypeInfo.ContainerLayout.Packed); + debug.assert(containerLayout(E3) == TypeInfo.ContainerLayout.Extern); + debug.assert(containerLayout(S1) == TypeInfo.ContainerLayout.Auto); + debug.assert(containerLayout(S2) == TypeInfo.ContainerLayout.Packed); + debug.assert(containerLayout(S3) == TypeInfo.ContainerLayout.Extern); + debug.assert(containerLayout(U1) == TypeInfo.ContainerLayout.Auto); + debug.assert(containerLayout(U2) == TypeInfo.ContainerLayout.Packed); + debug.assert(containerLayout(U3) == TypeInfo.ContainerLayout.Extern); +} + +pub fn definitions(comptime T: type) []TypeInfo.Definition { + return switch (@typeInfo(T)) { + TypeId.Struct => |info| info.defs, + TypeId.Enum => |info| info.defs, + TypeId.Union => |info| info.defs, + else => @compileError("Expected struct, enum or union type, found '" + ++ @typeName(T) ++ "'"), + }; +} + +test "std.meta.definitions" { + const E1 = enum.{ + A, + + fn a() void {} + }; + const S1 = struct.{ + fn a() void {} + }; + const U1 = union.{ + a: u8, + + fn a() void {} + }; + + const defs = comptime [][]TypeInfo.Definition.{ + definitions(E1), + definitions(S1), + definitions(U1), + }; + + inline for (defs) |def| { + debug.assert(def.len == 1); + debug.assert(comptime mem.eql(u8, def[0].name, "a")); + } +} + +pub fn definitionInfo(comptime T: type, comptime def_name: []const u8) TypeInfo.Definition { + inline for (comptime definitions(T)) |def| { + if (comptime mem.eql(u8, def.name, def_name)) + return def; + } + + @compileError("'" ++ @typeName(T) ++ "' has no definition '" ++ def_name ++ "'"); +} + +test "std.meta.definitionInfo" { + const E1 = enum.{ + A, + + fn a() void {} + }; + const S1 = struct.{ + fn a() void {} + }; + const U1 = union.{ + a: u8, + + fn a() void {} + }; + + const infos = comptime []TypeInfo.Definition.{ + definitionInfo(E1, "a"), + definitionInfo(S1, "a"), + definitionInfo(U1, "a"), + }; + + inline for (infos) |info| { + debug.assert(comptime mem.eql(u8, info.name, "a")); + debug.assert(!info.is_pub); + } +} + +pub fn fields(comptime T: type) switch (@typeInfo(T)) { + TypeId.Struct => []TypeInfo.StructField, + TypeId.Union => []TypeInfo.UnionField, + TypeId.ErrorSet => []TypeInfo.Error, + TypeId.Enum => []TypeInfo.EnumField, + else => @compileError("Expected struct, union, error set or enum type, found '" + ++ @typeName(T) ++ "'"), +} { + return switch (@typeInfo(T)) { + TypeId.Struct => |info| info.fields, + TypeId.Union => |info| info.fields, + TypeId.Enum => |info| info.fields, + TypeId.ErrorSet => |info| info.errors, + else => @compileError("Expected struct, union, error set or enum type, found '" + ++ @typeName(T) ++ "'"), + }; +} + +test "std.meta.fields" { + const E1 = enum.{ + A, + }; + const E2 = error.{A}; + const S1 = struct.{ + a: u8, + }; + const U1 = union.{ + a: u8, + }; + + const e1f = comptime fields(E1); + const e2f = comptime fields(E2); + const sf = comptime fields(S1); + const uf = comptime fields(U1); + + debug.assert(e1f.len == 1); + debug.assert(e2f.len == 1); + debug.assert(sf.len == 1); + debug.assert(uf.len == 1); + debug.assert(mem.eql(u8, e1f[0].name, "A")); + debug.assert(mem.eql(u8, e2f[0].name, "A")); + debug.assert(mem.eql(u8, sf[0].name, "a")); + debug.assert(mem.eql(u8, uf[0].name, "a")); + debug.assert(comptime sf[0].field_type == u8); + debug.assert(comptime uf[0].field_type == u8); +} + +pub fn fieldInfo(comptime T: type, comptime field_name: []const u8) switch (@typeInfo(T)) { + TypeId.Struct => TypeInfo.StructField, + TypeId.Union => TypeInfo.UnionField, + TypeId.ErrorSet => TypeInfo.Error, + TypeId.Enum => TypeInfo.EnumField, + else => @compileError("Expected struct, union, error set or enum type, found '" + ++ @typeName(T) ++ "'"), +} { + inline for (comptime fields(T)) |field| { + if (comptime mem.eql(u8, field.name, field_name)) + return field; + } + + @compileError("'" ++ @typeName(T) ++ "' has no field '" ++ field_name ++ "'"); +} + +test "std.meta.fieldInfo" { + const E1 = enum.{ + A, + }; + const E2 = error.{A}; + const S1 = struct.{ + a: u8, + }; + const U1 = union.{ + a: u8, + }; + + const e1f = comptime fieldInfo(E1, "A"); + const e2f = comptime fieldInfo(E2, "A"); + const sf = comptime fieldInfo(S1, "a"); + const uf = comptime fieldInfo(U1, "a"); + + debug.assert(mem.eql(u8, e1f.name, "A")); + debug.assert(mem.eql(u8, e2f.name, "A")); + debug.assert(mem.eql(u8, sf.name, "a")); + debug.assert(mem.eql(u8, uf.name, "a")); + debug.assert(comptime sf.field_type == u8); + debug.assert(comptime uf.field_type == u8); +} + +pub fn TagType(comptime T: type) type { + return switch (@typeInfo(T)) { + TypeId.Enum => |info| info.tag_type, + TypeId.Union => |info| if(info.tag_type) |Tag| Tag else null, + else => @compileError("expected enum or union type, found '" ++ @typeName(T) ++ "'"), + }; +} + +test "std.meta.TagType" { + const E = enum(u8).{ + C = 33, + D, + }; + const U = union(E).{ + C: u8, + D: u16, + }; + + debug.assert(TagType(E) == u8); + debug.assert(TagType(U) == E); +} + + + +///Returns the active tag of a tagged union +pub fn activeTag(u: var) @TagType(@typeOf(u)) +{ + const T = @typeOf(u); + return @TagType(T)(u); +} + +test "std.meta.activeTag" +{ + const UE = enum. + { + Int, + Float, + }; + + const U = union(UE). + { + Int: u32, + Float: f32, + }; + + var u = U.{ .Int = 32, }; + debug.assert(activeTag(u) == UE.Int); + + u = U.{ .Float = 112.9876, }; + debug.assert(activeTag(u) == UE.Float); + +} + +///Compares two of any type for equality. Containers are compared on a field-by-field basis, +/// where possible. Pointers are not followed. +pub fn eql(a: var, b: @typeOf(a)) bool +{ + const T = @typeOf(a); + + switch(@typeId(T)) + { + builtin.TypeId.Struct => + { + const info = @typeInfo(T).Struct; + + inline for(info.fields) |field_info| + { + if(!eql(@field(a, field_info.name), + @field(b, field_info.name))) return false; + } + return true; + }, + builtin.TypeId.ErrorUnion => + { + 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; + } + }, + builtin.TypeId.Union => + { + const info = @typeInfo(T).Union; + + if(info.tag_type) |_| + { + const tag_a = activeTag(a); + const tag_b = activeTag(b); + if(tag_a != tag_b) return false; + + inline for(info.fields) |field_info| + { + const enum_field = field_info.enum_field.?; + if(enum_field.value == @enumToInt(tag_a)) + { + return eql(@field(a, enum_field.name), + @field(b, enum_field.name)); + } + } + return false; + } + + @compileError("cannot compare untagged union type " ++ @typeName(T)); + }, + builtin.TypeId.Array => + { + if(a.len != b.len) return false; + for(a) |e, i| if(!eql(e, b[i])) return false; + return true; + }, + builtin.TypeId.Pointer => + { + const info = @typeInfo(T).Pointer; + switch(info.size) + { + builtin.TypeInfo.Pointer.Size.One, + builtin.TypeInfo.Pointer.Size.Many => return a == b, + builtin.TypeInfo.Pointer.Size.Slice => return a.ptr == b.ptr and a.len == b.len, + } + }, + else => return a == b, + } +} + + +test "std.meta.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", + }; + + const s_2 = S. + { + .a = 1, + .b = 123.3, + .c = "54321", + }; + + const 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, }; + + debug.assert(eql(s_1, s_3)); + debug.assert(eql(&s_1, &s_1)); + debug.assert(!eql(&s_1, &s_3)); + debug.assert(eql(u_1, u_3)); + debug.assert(!eql(u_1, u_2)); + + var a1 = "abcdef"; + var a2 = "abcdef"; + var a3 = "ghijkl"; + + debug.assert(eql(a1, a2)); + debug.assert(!eql(a1, a3)); + debug.assert(!eql(a1[0..], a2[0..])); + + const EU = struct. + { + fn tst(err: bool) !u8 + { + if(err) return error.Error; + return u8(5); + } + }; + + debug.assert(eql(EU.tst(true), EU.tst(true))); + debug.assert(eql(EU.tst(false), EU.tst(false))); + debug.assert(!eql(EU.tst(false), EU.tst(true))); +} \ No newline at end of file diff --git a/std/meta/trait.zig b/std/meta/trait.zig new file mode 100644 index 0000000000..4f5ab801d7 --- /dev/null +++ b/std/meta/trait.zig @@ -0,0 +1,449 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const mem = std.mem; +const debug = std.debug; +const warn = debug.warn; + +const meta = @import("index.zig"); + +//This is necessary if we want to return generic functions directly because of how the +// the type erasure works. see: #1375 +fn traitFnWorkaround(comptime T: type) bool +{ + return false; +} + +pub const TraitFn = @typeOf(traitFnWorkaround); + +/// + +//////Trait generators + +//Need TraitList because compiler can't do varargs at comptime yet +pub const TraitList = []const TraitFn; +pub fn multiTrait(comptime traits: TraitList) TraitFn +{ + const Closure = struct. + { + pub fn trait(comptime T: type) bool + { + inline for(traits) |t| if(!t(T)) return false; + return true; + } + }; + return Closure.trait; +} + +test "std.meta.trait.multiTrait" +{ + const Vector2 = struct. + { + const MyType = @This(); + + x: u8, + y: u8, + + pub fn add(self: MyType, other: MyType) MyType + { + return MyType. + { + .x = self.x + other.x, + .y = self.y + other.y, + }; + } + }; + + const isVector = multiTrait + ( + TraitList. + { + hasFn("add"), + hasField("x"), + hasField("y"), + } + ); + debug.assert(isVector(Vector2)); + debug.assert(!isVector(u8)); +} + +/// + +pub fn hasDef(comptime name: []const u8) TraitFn +{ + const Closure = struct. + { + pub fn trait(comptime T: type) bool + { + const info = @typeInfo(T); + const defs = switch(info) + { + builtin.TypeId.Struct => |s| s.defs, + builtin.TypeId.Union => |u| u.defs, + builtin.TypeId.Enum => |e| e.defs, + else => return false, + }; + + inline for(defs) |def| + { + if(mem.eql(u8, def.name, name)) return def.is_pub; + } + + return false; + } + }; + return Closure.trait; +} + +test "std.meta.trait.hasDef" +{ + const TestStruct = struct. + { + pub const value = u8(16); + }; + + const TestStructFail = struct. + { + const value = u8(16); + }; + + debug.assert(hasDef("value")(TestStruct)); + debug.assert(!hasDef("value")(TestStructFail)); + debug.assert(!hasDef("value")(*TestStruct)); + debug.assert(!hasDef("value")(**TestStructFail)); + debug.assert(!hasDef("x")(TestStruct)); + debug.assert(!hasDef("value")(u8)); +} + +/// +pub fn hasFn(comptime name: []const u8) TraitFn +{ + const Closure = struct. + { + pub fn trait(comptime T: type) bool + { + if(!comptime hasDef(name)(T)) return false; + const DefType = @typeOf(@field(T, name)); + const def_type_id = @typeId(DefType); + return def_type_id == builtin.TypeId.Fn; + } + }; + return Closure.trait; +} + +test "std.meta.trait.hasFn" +{ + const TestStruct = struct. + { + pub fn useless() void {} + }; + + debug.assert(hasFn("useless")(TestStruct)); + debug.assert(!hasFn("append")(TestStruct)); + debug.assert(!hasFn("useless")(u8)); +} + +/// +pub fn hasField(comptime name: []const u8) TraitFn +{ + const Closure = struct. + { + pub fn trait(comptime T: type) bool + { + const info = @typeInfo(T); + const fields = switch(info) + { + builtin.TypeId.Struct => |s| s.fields, + builtin.TypeId.Union => |u| u.fields, + builtin.TypeId.Enum => |e| e.fields, + else => return false, + }; + + inline for(fields) |field| + { + if(mem.eql(u8, field.name, name)) return true; + } + + return false; + } + }; + return Closure.trait; +} + +test "std.meta.trait.hasField" +{ + const TestStruct = struct. + { + value: u32, + }; + + debug.assert(hasField("value")(TestStruct)); + debug.assert(!hasField("value")(*TestStruct)); + debug.assert(!hasField("x")(TestStruct)); + debug.assert(!hasField("x")(**TestStruct)); + debug.assert(!hasField("value")(u8)); +} + +/// + +pub fn is(comptime id: builtin.TypeId) TraitFn +{ + const Closure = struct. + { + pub fn trait(comptime T: type) bool + { + return id == @typeId(T); + } + }; + return Closure.trait; +} + +test "std.meta.trait.is" +{ + debug.assert(is(builtin.TypeId.Int)(u8)); + debug.assert(!is(builtin.TypeId.Int)(f32)); + debug.assert(is(builtin.TypeId.Pointer)(*u8)); + debug.assert(is(builtin.TypeId.Void)(void)); + debug.assert(!is(builtin.TypeId.Optional)(error)); +} + +/// + +pub fn isPtrTo(comptime id: builtin.TypeId) TraitFn +{ + const Closure = struct. + { + pub fn trait(comptime T: type) bool + { + if(!comptime isSingleItemPtr(T)) return false; + return id == @typeId(meta.Child(T)); + } + }; + return Closure.trait; +} + +test "std.meta.trait.isPtrTo" +{ + debug.assert(!isPtrTo(builtin.TypeId.Struct)(struct.{})); + debug.assert(isPtrTo(builtin.TypeId.Struct)(*struct.{})); + debug.assert(!isPtrTo(builtin.TypeId.Struct)(**struct.{})); +} + + +///////////Strait trait Fns + +//@TODO: +// Somewhat limited since we can't apply this logic to normal variables, fields, or +// Fns yet. Should be isExternType? +pub fn isExtern(comptime T: type) bool +{ + const Extern = builtin.TypeInfo.ContainerLayout.Extern; + const info = @typeInfo(T); + return switch(info) + { + builtin.TypeId.Struct => |s| s.layout == Extern, + builtin.TypeId.Union => |u| u.layout == Extern, + builtin.TypeId.Enum => |e| e.layout == Extern, + else => false, + }; +} + +test "std.meta.trait.isExtern" +{ + const TestExStruct = extern struct.{}; + const TestStruct = struct.{}; + + debug.assert(isExtern(TestExStruct)); + debug.assert(!isExtern(TestStruct)); + debug.assert(!isExtern(u8)); +} + +/// + +pub fn isPacked(comptime T: type) bool +{ + const Packed = builtin.TypeInfo.ContainerLayout.Packed; + const info = @typeInfo(T); + return switch(info) + { + builtin.TypeId.Struct => |s| s.layout == Packed, + builtin.TypeId.Union => |u| u.layout == Packed, + builtin.TypeId.Enum => |e| e.layout == Packed, + else => false, + }; +} + +test "std.meta.trait.isPacked" +{ + const TestPStruct = packed struct.{}; + const TestStruct = struct.{}; + + debug.assert(isPacked(TestPStruct)); + debug.assert(!isPacked(TestStruct)); + debug.assert(!isPacked(u8)); +} + +/// + +pub fn isSingleItemPtr(comptime T: type) bool +{ + if(comptime is(builtin.TypeId.Pointer)(T)) + { + const info = @typeInfo(T); + return info.Pointer.size == builtin.TypeInfo.Pointer.Size.One; + } + return false; +} + +test "std.meta.trait.isSingleItemPtr" +{ + const array = []u8.{0} ** 10; + debug.assert(isSingleItemPtr(@typeOf(&array[0]))); + debug.assert(!isSingleItemPtr(@typeOf(array))); + debug.assert(!isSingleItemPtr(@typeOf(array[0..1]))); +} + +/// + +pub fn isManyItemPtr(comptime T: type) bool +{ + if(comptime is(builtin.TypeId.Pointer)(T)) + { + const info = @typeInfo(T); + return info.Pointer.size == builtin.TypeInfo.Pointer.Size.Many; + } + return false; +} + +test "std.meta.trait.isManyItemPtr" +{ + const array = []u8.{0} ** 10; + const mip = @ptrCast([*]const u8, &array[0]); + debug.assert(isManyItemPtr(@typeOf(mip))); + debug.assert(!isManyItemPtr(@typeOf(array))); + debug.assert(!isManyItemPtr(@typeOf(array[0..1]))); +} + +/// + +pub fn isSlice(comptime T: type) bool +{ + if(comptime is(builtin.TypeId.Pointer)(T)) + { + const info = @typeInfo(T); + return info.Pointer.size == builtin.TypeInfo.Pointer.Size.Slice; + } + return false; +} + +test "std.meta.trait.isSlice" +{ + const array = []u8.{0} ** 10; + debug.assert(isSlice(@typeOf(array[0..]))); + debug.assert(!isSlice(@typeOf(array))); + debug.assert(!isSlice(@typeOf(&array[0]))); +} + +/// + +pub fn isIndexable(comptime T: type) bool +{ + if(comptime is(builtin.TypeId.Pointer)(T)) + { + const info = @typeInfo(T); + if(info.Pointer.size == builtin.TypeInfo.Pointer.Size.One) + { + if(comptime is(builtin.TypeId.Array)(meta.Child(T))) return true; + return false; + } + return true; + } + return comptime is(builtin.TypeId.Array)(T); +} + +test "std.meta.trait.isIndexable" +{ + const array = []u8.{0} ** 10; + const slice = array[0..]; + + debug.assert(isIndexable(@typeOf(array))); + debug.assert(isIndexable(@typeOf(&array))); + debug.assert(isIndexable(@typeOf(slice))); + debug.assert(!isIndexable(meta.Child(@typeOf(slice)))); +} + +/// + +pub fn isNumber(comptime T: type) bool +{ + return switch(@typeId(T)) + { + builtin.TypeId.Int, + builtin.TypeId.Float, + builtin.TypeId.ComptimeInt, + builtin.TypeId.ComptimeFloat => true, + else => false, + }; +} + +test "std.meta.trait.isNumber" +{ + const NotANumber = struct. + { + number: u8, + }; + + debug.assert(isNumber(u32)); + debug.assert(isNumber(f32)); + debug.assert(isNumber(u64)); + debug.assert(isNumber(@typeOf(102))); + debug.assert(isNumber(@typeOf(102.123))); + debug.assert(!isNumber([]u8)); + debug.assert(!isNumber(NotANumber)); +} + +/// + +pub fn isConstPtr(comptime T: type) bool +{ + if(!comptime is(builtin.TypeId.Pointer)(T)) return false; + const info = @typeInfo(T); + return info.Pointer.is_const; +} + +test "std.meta.trait.isConstPtr" +{ + var t = u8(0); + const c = u8(0); + debug.assert(isConstPtr(*const @typeOf(t))); + debug.assert(isConstPtr(@typeOf(&c))); + debug.assert(!isConstPtr(*@typeOf(t))); + debug.assert(!isConstPtr(@typeOf(6))); +} + +/// + +pub fn isContainer(comptime T: type) bool +{ + const info = @typeInfo(T); + return switch(info) + { + builtin.TypeId.Struct => true, + builtin.TypeId.Union => true, + builtin.TypeId.Enum => true, + else => false, + }; +} + +test "std.meta.trait.isContainer" +{ + const TestStruct = struct.{}; + const TestUnion = union.{ a: void, }; + const TestEnum = enum.{ A, B, }; + + debug.assert(isContainer(TestStruct)); + debug.assert(isContainer(TestUnion)); + debug.assert(isContainer(TestEnum)); + debug.assert(!isContainer(u8)); +} + +/// \ No newline at end of file