Skip to content

Commit 0b7df75

Browse files
authored
Fix empty keybind setting (#5977)
Fixes #5936. Extracts the default keybind setting to an `init` function. Add logic to call `init` in `parseIntoField` if it is defined for the type.
2 parents 22f3e60 + df9de15 commit 0b7df75

File tree

2 files changed

+663
-619
lines changed

2 files changed

+663
-619
lines changed

src/cli/args.zig

+39-10
Original file line numberDiff line numberDiff line change
@@ -247,28 +247,34 @@ pub fn parseIntoField(
247247

248248
inline for (info.Struct.fields) |field| {
249249
if (field.name[0] != '_' and mem.eql(u8, field.name, key)) {
250+
// For optional fields, we just treat it as the child type.
251+
// This lets optional fields default to null but get set by
252+
// the CLI.
253+
const Field = switch (@typeInfo(field.type)) {
254+
.Optional => |opt| opt.child,
255+
else => field.type,
256+
};
257+
const fieldInfo = @typeInfo(Field);
258+
const canHaveDecls = fieldInfo == .Struct or fieldInfo == .Union or fieldInfo == .Enum;
259+
250260
// If the value is empty string (set but empty string),
251261
// then we reset the value to the default.
252262
if (value) |v| default: {
253263
if (v.len != 0) break :default;
264+
// Set default value if possible.
265+
if (canHaveDecls and @hasDecl(Field, "init")) {
266+
try @field(dst, field.name).init(alloc);
267+
return;
268+
}
254269
const raw = field.default_value orelse break :default;
255270
const ptr: *const field.type = @alignCast(@ptrCast(raw));
256271
@field(dst, field.name) = ptr.*;
257272
return;
258273
}
259274

260-
// For optional fields, we just treat it as the child type.
261-
// This lets optional fields default to null but get set by
262-
// the CLI.
263-
const Field = switch (@typeInfo(field.type)) {
264-
.Optional => |opt| opt.child,
265-
else => field.type,
266-
};
267-
268275
// If we are a type that can have decls and have a parseCLI decl,
269276
// we call that and use that to set the value.
270-
const fieldInfo = @typeInfo(Field);
271-
if (fieldInfo == .Struct or fieldInfo == .Union or fieldInfo == .Enum) {
277+
if (canHaveDecls) {
272278
if (@hasDecl(Field, "parseCLI")) {
273279
const fnInfo = @typeInfo(@TypeOf(Field.parseCLI)).Fn;
274280
switch (fnInfo.params.len) {
@@ -761,6 +767,29 @@ test "parseIntoField: ignore underscore-prefixed fields" {
761767
try testing.expectEqualStrings("12", data._a);
762768
}
763769

770+
test "parseIntoField: struct with init func" {
771+
const testing = std.testing;
772+
var arena = ArenaAllocator.init(testing.allocator);
773+
defer arena.deinit();
774+
const alloc = arena.allocator();
775+
776+
var data: struct {
777+
a: struct {
778+
const Self = @This();
779+
780+
v: []const u8,
781+
782+
pub fn init(self: *Self, _alloc: Allocator) !void {
783+
_ = _alloc;
784+
self.* = .{ .v = "HELLO!" };
785+
}
786+
},
787+
} = undefined;
788+
789+
try parseIntoField(@TypeOf(data), alloc, &data, "a", "");
790+
try testing.expectEqual(@as([]const u8, "HELLO!"), data.a.v);
791+
}
792+
764793
test "parseIntoField: string" {
765794
const testing = std.testing;
766795
var arena = ArenaAllocator.init(testing.allocator);

0 commit comments

Comments
 (0)