const std = @import("std"); /// Returns a type having fields that type A and type B have. /// Fields from B are more preferred than from A in case that field names are duplicated. pub fn Merged(comptime A: type, comptime B: type) type { const at = @typeInfo(A).@"struct"; const bt = @typeInfo(B).@"struct"; return @Type(.{ .@"struct" = .{ .layout = .auto, .fields = mergeFields(at.fields, bt.fields), .is_tuple = false, .decls = &.{}, }, }); } fn mergeFields( comptime a: []const std.builtin.Type.StructField, comptime b: []const std.builtin.Type.StructField) []std.builtin.Type.StructField { var ret: [a.len+b.len]std.builtin.Type.StructField = undefined; var len: usize = 0; inline for (b) |f| { ret[len] = f; ret[len].is_comptime = false; len += 1; } inline for (a) |f| { inline for (b) |fb| { if (std.mem.eql(u8, f.name, fb.name)) { break; } } else { ret[len] = f; ret[len].is_comptime = false; len += 1; } } return ret[0..len]; } /// Same to `Merged(@TypeOf(a), @TypeOf(b))` but returns a merged value, not type. pub fn merge(a: anytype, b: anytype) Merged(@TypeOf(a), @TypeOf(b)) { var ret: Merged(@TypeOf(a), @TypeOf(b)) = undefined; const af = @typeInfo(@TypeOf(a)).@"struct".fields; const bf = @typeInfo(@TypeOf(b)).@"struct".fields; inline for (bf) |f| { @field(ret, f.name) = @field(b, f.name); } inline for (af) |f| { inline for (bf) |fb| { if (std.mem.eql(u8, f.name, fb.name)) { break; } } else { @field(ret, f.name) = @field(a, f.name); } } return ret; } test "merging 2 structs" { const merged = merge(.{.a = 0, .b = 1, .c = 2,}, .{.b = 44, .d = 53,}); try std.testing.expect(merged.a == 0); try std.testing.expect(merged.b == 44); try std.testing.expect(merged.c == 2); try std.testing.expect(merged.d == 53); }