const std = @import("std"); fn Args(params: []const std.builtin.Type.Fn.Param) type { var argTypes: [params.len]type = undefined; for (params, 0..) |p, i| { argTypes[i] = p.type.?; } return @Tuple(&argTypes); } fn Val(comptime T: type) type { const ti = @typeInfo(T).@"struct"; const fieldCount = ti.fields.len + ti.decls.len + 1; var names: [fieldCount][]const u8 = undefined; var types: [fieldCount]type = undefined; var attrs: [fieldCount]std.builtin.Type.StructField.Attributes = undefined; inline for (ti.fields, 0..) |field, idx| { if (field.is_comptime) @compileError("implement skipping comptime fields"); const fi = @typeInfo(field.type); switch (fi) { .@"struct" => { names[idx] = field.name; const NT = Val(field.type); types[idx] = NT; attrs[idx] = .{ .default_value_ptr = &NT{}, }; }, else => { names[idx] = field.name; const NT = struct { pub fn get(self: *@This()) field.type { std.debug.print("getting {s}\n", .{field.name}); const parent = self.valuePtr(); return @field(parent.value, field.name); } pub fn set(self: *@This(), value: field.type) void { std.debug.print("setting {s}\n", .{field.name}); const parent = self.valuePtr(); @field(parent.value, field.name) = value; } fn valuePtr(self: *@This()) *align(@alignOf(@This())) Val(T) { const parent: *align(@alignOf(@This())) Val(T) = @fieldParentPtr(field.name, self); return parent; } }; types[idx] = NT; attrs[idx] = .{ .default_value_ptr = &NT{}, }; }, } } inline for (ti.decls, 0..) |decl, idx| { names[ti.fields.len + idx] = decl.name; const fi = @typeInfo(@TypeOf(@field(T, decl.name))).@"fn"; const NT = struct { pub fn call(self: @This(), args: Args(fi.params)) (fi.return_type orelse void) { _ = self; std.debug.print("calling {s}\n", .{decl.name}); @call(.auto, @field(T, decl.name), args); std.debug.print("done calling {s}\n", .{decl.name}); } }; types[ti.fields.len + idx] = NT; attrs[ti.fields.len + idx] = .{ .default_value_ptr = &NT{} }; } names[fieldCount - 1] = "value"; types[fieldCount - 1] = T; attrs[fieldCount - 1] = .{ .@"comptime" = false, .@"align" = @alignOf(T), .default_value_ptr = &T{}, }; return @Struct(.auto, null, &names, &types, &attrs); } pub fn main() !void { var a = Val(struct { i: i32 = 123, j: i32 = 456, s: struct { i: i32 = 789, } = .{}, pub fn b() void { std.debug.print("B!\n", .{}); } pub fn c(self: @This()) void { std.debug.print("C! ({})\n", .{self}); } pub fn d(self: @This(), i: i32) void { self.i += i; std.debug.print("D! ({})\n", .{self}); } }){}; std.debug.print("[{}] {}\n{}\n", .{ @TypeOf(a), a, a.value }); // a.b.call(.{}); // a.c.call(.{a}); // a.c.call(.{a, 5}); std.debug.print("{}\n", .{a.i.get()}); std.debug.print("{}\n", .{a.j.get()}); a.j.set(9); std.debug.print("{}\n", .{a.j.get()}); std.debug.print("{}\n", .{a.s.i.get()}); }