2022-11-26 20:29:23 -08:00
|
|
|
const std = @import("std");
|
|
|
|
const StructField = std.builtin.Type.StructField;
|
|
|
|
|
|
|
|
/// Given a type and a struct literal of defaults to add, this function creates
|
|
|
|
/// a simulacrum type with additional defaults set on its fields.
|
|
|
|
///
|
|
|
|
/// This function cannot remove default values from fields, but it can add some
|
|
|
|
/// to fields that don't have them, and it can overwrite existing defaults
|
|
|
|
pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
|
|
|
|
comptime {
|
|
|
|
const inputInfo = @typeInfo(input);
|
|
|
|
const fieldcount = switch (inputInfo) {
|
2022-11-27 01:31:20 -08:00
|
|
|
.Struct => |spec| blk: {
|
2022-11-26 20:29:23 -08:00
|
|
|
if (spec.decls.len > 0) {
|
|
|
|
@compileError("UpdateDefaults only works on structs " ++
|
|
|
|
"without decls due to limitations in @Type.");
|
|
|
|
}
|
2022-11-27 01:31:20 -08:00
|
|
|
break :blk spec.fields.len;
|
2022-11-26 20:29:23 -08:00
|
|
|
},
|
|
|
|
else => @compileError("can only add default value to struct type"),
|
|
|
|
};
|
|
|
|
|
|
|
|
var fields: [fieldcount]StructField = undefined;
|
2023-03-20 23:02:13 -07:00
|
|
|
for (inputInfo.Struct.fields, 0..) |field, idx| {
|
2022-11-26 20:29:23 -08:00
|
|
|
fields[idx] = .{
|
|
|
|
.name = field.name,
|
|
|
|
.field_type = field.field_type,
|
|
|
|
// the cast ostensibly does type checking for us. It also makes
|
|
|
|
// setting null defaults work, and it converts comptime_int to
|
|
|
|
// the appropriate type, which is nice for ergonomics. Not sure
|
|
|
|
// if it introduces weird edge cases. Probably it's fine?
|
|
|
|
.default_value = if (@hasField(@TypeOf(defaults), field.name))
|
|
|
|
@ptrCast(?*const anyopaque, &@as(field.field_type, @field(defaults, field.name)))
|
|
|
|
else
|
|
|
|
field.default_value,
|
|
|
|
.is_comptime = field.is_comptime,
|
|
|
|
.alignment = field.alignment,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return @Type(.{ .Struct = .{
|
|
|
|
.layout = inputInfo.Struct.layout,
|
|
|
|
.backing_integer = inputInfo.Struct.backing_integer,
|
|
|
|
.fields = &fields,
|
|
|
|
.decls = inputInfo.Struct.decls,
|
|
|
|
.is_tuple = inputInfo.Struct.is_tuple,
|
|
|
|
} });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-25 16:08:59 -07:00
|
|
|
pub fn enum_length(comptime T: type) comptime_int {
|
|
|
|
return @typeInfo(T).Enum.fields.len;
|
|
|
|
}
|
|
|
|
|
2023-03-28 01:07:02 -07:00
|
|
|
pub fn SliceIterator(comptime T: type) type {
|
|
|
|
// could be expanded to use std.meta.Elem, perhaps
|
|
|
|
const ResultType = std.meta.Child(T);
|
|
|
|
|
|
|
|
return struct {
|
|
|
|
index: usize,
|
|
|
|
data: T,
|
|
|
|
|
|
|
|
pub const InitError = error{};
|
|
|
|
|
|
|
|
pub fn wrap(value: T) @This() {
|
|
|
|
return @This(){ .index = 0, .data = value };
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn next(self: *@This()) ?ResultType {
|
|
|
|
if (self.index == self.data.len) return null;
|
|
|
|
|
|
|
|
defer self.index += 1;
|
|
|
|
return self.data[self.index];
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn peek(self: *@This()) ?ResultType {
|
|
|
|
if (self.index == self.data.len) return null;
|
|
|
|
|
|
|
|
return self.data[self.index];
|
|
|
|
}
|
|
|
|
|
2023-03-28 23:35:54 -07:00
|
|
|
pub fn rewind(self: *@This()) void {
|
|
|
|
if (self.index == 0) return;
|
|
|
|
|
|
|
|
self.index -= 1;
|
|
|
|
}
|
|
|
|
|
2023-03-28 01:07:02 -07:00
|
|
|
pub fn skip(self: *@This()) void {
|
|
|
|
if (self.index == self.data.len) return;
|
|
|
|
|
|
|
|
self.index += 1;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-29 23:10:42 -07:00
|
|
|
pub fn copy_struct(comptime T: type, source: T, field_overrides: anytype) T {
|
|
|
|
var result: T = undefined;
|
|
|
|
|
|
|
|
comptime inline for (@typeInfo(@TypeOf(field_overrides)).Struct.fields) |field| {
|
|
|
|
if (!@hasField(T, field.name)) @compileError("override contains bad field" ++ field);
|
|
|
|
};
|
|
|
|
|
|
|
|
inline for (comptime @typeInfo(T).Struct.fields) |field| {
|
|
|
|
if (comptime @hasField(@TypeOf(field_overrides), field.name))
|
|
|
|
@field(result, field.name) = @field(field_overrides, field.name)
|
|
|
|
else
|
|
|
|
@field(result, field.name) = @field(source, field.name);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-11-27 01:31:20 -08:00
|
|
|
/// Stores type-erased pointers to items in comptime extensible data structures,
|
|
|
|
/// which allows e.g. assembling a tuple through multiple calls rather than all
|
|
|
|
/// at once.
|
2023-04-01 13:04:02 -07:00
|
|
|
pub const TupleBuilder = struct {
|
2022-11-27 01:31:20 -08:00
|
|
|
pointers: []const *const anyopaque = &[0]*const anyopaque{},
|
|
|
|
types: []const type = &[0]type{},
|
|
|
|
|
|
|
|
pub fn add(comptime self: *@This(), comptime item: anytype) void {
|
2023-03-30 17:00:49 -07:00
|
|
|
self.pointers = self.pointers ++ &[_]*const anyopaque{@as(*const anyopaque, &item)};
|
|
|
|
self.types = self.types ++ &[_]type{@TypeOf(item)};
|
2022-11-27 01:31:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn retrieve(comptime self: @This(), comptime index: comptime_int) self.types[index] {
|
|
|
|
return @ptrCast(*const self.types[index], @alignCast(@alignOf(*const self.types[index]), self.pointers[index])).*;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn realTuple(comptime self: @This()) self.TupleType() {
|
|
|
|
comptime {
|
|
|
|
var result: self.TupleType() = undefined;
|
|
|
|
var idx = 0;
|
|
|
|
while (idx < self.types.len) : (idx += 1) {
|
|
|
|
result[idx] = self.retrieve(idx);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn TupleType(comptime self: @This()) type {
|
|
|
|
comptime {
|
|
|
|
var fields: [self.types.len]StructField = undefined;
|
2023-03-20 23:02:13 -07:00
|
|
|
for (self.types, 0..) |Type, idx| {
|
2022-11-27 01:31:20 -08:00
|
|
|
var num_buf: [128]u8 = undefined;
|
|
|
|
fields[idx] = .{
|
2023-03-25 16:08:59 -07:00
|
|
|
.name = std.fmt.bufPrint(&num_buf, "{d}", .{idx}) catch @compileError("failed to write field"),
|
2023-03-20 23:02:13 -07:00
|
|
|
.type = Type,
|
2022-11-27 01:31:20 -08:00
|
|
|
.default_value = null,
|
|
|
|
// TODO: is this the right thing to do?
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = if (@sizeOf(Type) > 0) @alignOf(Type) else 0,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return @Type(.{ .Struct = .{
|
|
|
|
.layout = .Auto,
|
|
|
|
.fields = &fields,
|
|
|
|
.decls = &.{},
|
|
|
|
.is_tuple = true,
|
|
|
|
} });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-26 20:29:23 -08:00
|
|
|
test "add basic default" {
|
|
|
|
const Base = struct { a: u8 };
|
|
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = 4 });
|
|
|
|
|
|
|
|
const value = Defaulted{};
|
|
|
|
try std.testing.expectEqual(@as(u8, 4), value.a);
|
|
|
|
}
|
|
|
|
|
|
|
|
test "overwrite basic default" {
|
|
|
|
const Base = struct { a: u8 = 0 };
|
|
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = 1 });
|
|
|
|
|
|
|
|
const value = Defaulted{};
|
|
|
|
try std.testing.expectEqual(@as(u8, 1), value.a);
|
|
|
|
}
|
|
|
|
|
|
|
|
test "add string default" {
|
|
|
|
const Base = struct { a: []const u8 };
|
|
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = "hello" });
|
|
|
|
|
|
|
|
const value = Defaulted{};
|
|
|
|
try std.testing.expectEqual(@as([]const u8, "hello"), value.a);
|
|
|
|
}
|
|
|
|
|
|
|
|
test "add null default" {
|
|
|
|
const Base = struct { a: ?u8 };
|
|
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = null });
|
|
|
|
|
|
|
|
const value = Defaulted{};
|
|
|
|
try std.testing.expectEqual(@as(?u8, null), value.a);
|
|
|
|
}
|
|
|
|
|
|
|
|
test "add enum default" {
|
|
|
|
const Options = enum { good, bad };
|
|
|
|
const Base = struct { a: Options };
|
|
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = .good });
|
|
|
|
|
|
|
|
const value = Defaulted{};
|
|
|
|
try std.testing.expectEqual(Options.good, value.a);
|
|
|
|
}
|
|
|
|
|
|
|
|
test "preserve existing default" {
|
|
|
|
const Base = struct { a: ?u8 = 2, b: u8 };
|
|
|
|
const Defaulted = UpdateDefaults(Base, .{ .b = 3 });
|
|
|
|
|
|
|
|
const value = Defaulted{};
|
|
|
|
try std.testing.expectEqual(@as(?u8, 2), value.a);
|
|
|
|
try std.testing.expectEqual(@as(?u8, 3), value.b);
|
|
|
|
}
|
|
|
|
|
|
|
|
test "add multiple defaults" {
|
|
|
|
const Base = struct { a: u8, b: i8, c: ?u8 };
|
|
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = 3, .c = 2 });
|
|
|
|
|
|
|
|
const value = Defaulted{ .b = -1 };
|
|
|
|
try std.testing.expectEqual(@as(u8, 3), value.a);
|
|
|
|
try std.testing.expectEqual(@as(i8, -1), value.b);
|
|
|
|
try std.testing.expectEqual(@as(?u8, 2), value.c);
|
|
|
|
}
|