The user can provide a context type and corresponding value that will get passed into any executed callbacks. This allows for complex behavior through side effects and provides a mechanism by which the user can pass an allocator into argument handlers, etc. There was also a lot of restructuring in this including a bit more automagical behavior, like making parameters that wrap optional types default to being optional. The start of automatic handler picking (user overridable, of course) is in place as well. Needing to specify the userdata context type makes things a bit more verbose, and there's some other jank I'm interested in trying to remove. I have some ideas, but I don't know how far I can go in my abuse of the compiler. However, this seems like it will be usable once I get around to writing the help text generation.
110 lines
3.8 KiB
Zig
110 lines
3.8 KiB
Zig
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) {
|
|
.Struct => |spec| {
|
|
if (spec.decls.len > 0) {
|
|
@compileError("UpdateDefaults only works on structs " ++
|
|
"without decls due to limitations in @Type.");
|
|
}
|
|
break spec.fields.len;
|
|
},
|
|
else => @compileError("can only add default value to struct type"),
|
|
};
|
|
|
|
var fields: [fieldcount]StructField = undefined;
|
|
for (inputInfo.Struct.fields) |field, idx| {
|
|
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,
|
|
} });
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|