const std = @import("std"); usingnamespace @import("./global.zig"); const TagSize = u15; const AddressableSize = u49; const TaggedPointer = packed struct { _ptr: AddressableSize, data: TagSize, pub inline fn init(ptr: anytype, data: TagSize) TaggedPointer { const Ptr = @TypeOf(ptr); if (comptime @typeInfo(Ptr) != .Pointer and Ptr != ?*c_void) { @compileError(@typeName(Ptr) ++ " must be a ptr, received: " ++ @tagName(@typeInfo(Ptr))); } const address = @ptrToInt(ptr); return TaggedPointer{ ._ptr = @truncate(AddressableSize, address), .data = data, }; } pub inline fn get(this: TaggedPointer, comptime Type: type) *Type { return @intToPtr(*Type, @intCast(usize, this._ptr)); } pub inline fn from(val: anytype) TaggedPointer { const ValueType = @TypeOf(val); return switch (ValueType) { f64, i64, u64 => @bitCast(TaggedPointer, val), ?*c_void, *c_void => @bitCast(TaggedPointer, @ptrToInt(val)), else => @compileError("Unsupported type: " ++ @typeName(ValueType)), }; } pub inline fn to(this: TaggedPointer) *c_void { return @intToPtr(*c_void, @bitCast(u64, this)); } }; pub fn TaggedPointerUnion(comptime Types: anytype) type { const TagType: type = tag_break: { var enumFields: [Types.len]std.builtin.TypeInfo.EnumField = undefined; var decls = [_]std.builtin.TypeInfo.Declaration{}; inline for (Types) |field, i| { enumFields[i] = .{ .name = @typeName(field), .value = 1024 - i, }; } break :tag_break @Type(.{ .Enum = .{ .layout = .Auto, .tag_type = TagSize, .fields = &enumFields, .decls = &decls, .is_exhaustive = false, }, }); }; return struct { pub const Tag = TagType; repr: TaggedPointer, const This = @This(); fn assert_type(comptime Type: type) void { if (!comptime @hasField(Tag, @typeName(Type))) { @compileError("TaggedPointerUnion does not have " ++ @typeName(Type) ++ "."); } } pub inline fn get(this: This, comptime Type: anytype) ?*Type { comptime assert_type(Type); return if (this.is(Type)) this.as(Type) else null; } pub inline fn tag(this: This) TagType { return @intToEnum(TagType, this.repr.data); } /// unsafely cast a tagged pointer to a specific type, without checking that it's really that type pub inline fn as(this: This, comptime Type: type) *Type { comptime assert_type(Type); return this.repr.get(Type); } pub inline fn is(this: This, comptime Type: type) bool { comptime assert_type(Type); return this.repr.data == comptime @enumToInt(@field(Tag, @typeName(Type))); } pub inline fn isValidPtr(_ptr: ?*c_void) bool { return This.isValid(This.from(_ptr)); } pub inline fn isValid(this: This) bool { return switch (this.repr.data) { @enumToInt( @field(Tag, @typeName(Types[Types.len - 1])), )...@enumToInt( @field(Tag, @typeName(Types[0])), ) => true, else => false, }; } pub inline fn from(_ptr: ?*c_void) This { return This{ .repr = TaggedPointer.from(_ptr) }; } pub inline fn ptr(this: This) *c_void { return this.repr.to(); } pub inline fn init(_ptr: anytype) This { const Type = std.meta.Child(@TypeOf(_ptr)); // there will be a compiler error if the passed in type doesn't exist in the enum return This{ .repr = TaggedPointer.init(_ptr, @enumToInt(@field(Tag, @typeName(Type)))) }; } }; } test "TaggedPointerUnion" { const IntPrimtiive = struct { val: u32 = 0 }; const StringPrimitive = struct { val: []const u8 = "" }; const Object = struct { blah: u32, val: u32 }; // const Invalid = struct { // wrong: bool = true, // }; const Union = TaggedPointerUnion(.{ IntPrimtiive, StringPrimitive, Object }); var str = try default_allocator.create(StringPrimitive); str.* = StringPrimitive{ .val = "hello!" }; var un = Union.init(str); try std.testing.expect(un.is(StringPrimitive)); try std.testing.expectEqualStrings(un.as(StringPrimitive).val, "hello!"); try std.testing.expect(!un.is(IntPrimtiive)); const num = try default_allocator.create(IntPrimtiive); num.val = 9999; var un2 = Union.init(num); try std.testing.expect(un2.as(IntPrimtiive).val == 9999); try std.testing.expect(un.tag() == .StringPrimitive); try std.testing.expect(un2.tag() == .IntPrimtiive); un2.repr.data = 0; try std.testing.expect(un2.tag() != .IntPrimtiive); try std.testing.expect(un2.get(IntPrimtiive) == null); // try std.testing.expect(un2.is(Invalid) == false); } test "TaggedPointer" { const Hello = struct { what: []const u8, }; var hello_struct_ptr = try default_allocator.create(Hello); hello_struct_ptr.* = Hello{ .what = "hiiii" }; var tagged = TaggedPointer.init(hello_struct_ptr, 0); try std.testing.expectEqual(tagged.get(Hello), hello_struct_ptr); try std.testing.expectEqualStrings(tagged.get(Hello).what, hello_struct_ptr.what); tagged = TaggedPointer.init(hello_struct_ptr, 100); try std.testing.expectEqual(tagged.get(Hello), hello_struct_ptr); try std.testing.expectEqualStrings(tagged.get(Hello).what, hello_struct_ptr.what); tagged = TaggedPointer.init(hello_struct_ptr, std.math.maxInt(TagSize) - 500); try std.testing.expectEqual(tagged.get(Hello), hello_struct_ptr); try std.testing.expectEqual(tagged.data, std.math.maxInt(TagSize) - 500); try std.testing.expectEqualStrings(tagged.get(Hello).what, hello_struct_ptr.what); var i: TagSize = 0; while (i < std.math.maxInt(TagSize) - 1) : (i += 1) { hello_struct_ptr = try default_allocator.create(Hello); const what = try std.fmt.allocPrint(default_allocator, "hiiii {d}", .{i}); hello_struct_ptr.* = Hello{ .what = what }; try std.testing.expectEqualStrings(TaggedPointer.from(TaggedPointer.init(hello_struct_ptr, i).to()).get(Hello).what, what); var this = TaggedPointer.from(TaggedPointer.init(hello_struct_ptr, i).to()); try std.testing.expect(this.data == i); try std.testing.expect(this.data != i + 1); } }