Skip to content

Instantly share code, notes, and snippets.

@adrusi
Created June 21, 2020 20:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adrusi/54ed2be2fbc6e9fb0c68f3c6f8706f9b to your computer and use it in GitHub Desktop.
Save adrusi/54ed2be2fbc6e9fb0c68f3c6f8706f9b to your computer and use it in GitHub Desktop.
Tuples in Zig 0.6.0
const std = @import("std");
const bufFmt = std.fmt.bufPrint;
pub fn compileErrorFmt(comptime fmt: []const u8, comptime args: var) void {
comptime {
var buf: [4096]u8 = undefined;
@compileError(@as([]const u8, bufFmt(&buf, fmt, args) catch |_| {
@compileError("compileFmt: output is too long. Cannot output @compileLogs longer than 4096 bytes");
}));
}
}
pub fn logPotentialErrors(comptime T: type) void {
comptime {
var buf: [4096]u8 = undefined;
@compileLog("Displaying potential errors in type:", @typeName(T));
var tinfo = @typeInfo(T);
while (true) {
switch (tinfo) {
.ErrorSet => |err_set| {
const errs = err_set orelse {
@compileLog("Could not determine error set");
};
for (errs) |err| {
@compileLog(err.name);
}
return;
},
.ErrorUnion => |err_union| {
tinfo = @typeInfo(err_union.error_set);
},
.Fn => |reg_fn| {
tinfo = @typeInfo(reg_fn.return_type orelse {
@compileLog("Could not determine error set: function return type cannot be determined");
return;
});
},
.BoundFn => |bound_fn| {
tinfo = @typeInfo(bound_fn.return_type orelse {
@compileLog("Could not determine error set: function return type cannot be determined");
return;
});
},
else => {
@compileLog("logPotentialErrors is not defined for", @tagName(tinfo));
return;
}
}
}
}
}
/// Convert types into their more general version. T is always assignable to GeneralizedType(T).
/// Currently, this only converts the type of string literals to []const u8.
pub fn GeneralizedType(comptime T: type) type {
const tinfo = @typeInfo(T);
switch (tinfo) {
.Pointer => |ptr_info| {
const child_tinfo = @typeInfo(ptr_info.child);
if (child_tinfo != .Array) return T;
if (child_tinfo.Array.child != u8) return T;
if (child_tinfo.Array.sentinel == null) return T;
if (child_tinfo.Array.sentinel.? != 0) return T;
return []const u8;
},
else => return T,
}
}
const std = @import("std");
const expectEqual = std.testing.expectEqual;
const comptime_utils = @import("comptime_utils.zig");
pub fn Tuple(comptime types: var) type {
comptime {
return switch (types.len) {
0 => Tuple0,
1 => Tuple1(types[0]),
2 => Tuple2(types[0], types[1]),
3 => Tuple3(types[0], types[1], types[2]),
4 => Tuple4(types[0], types[1], types[2], types[3]),
5 => Tuple5(types[0], types[1], types[2], types[3], types[4]),
else => @compileError("Tuples above length 5 not supported"),
};
}
}
pub fn tuple(elems: var) TupleReturn(@TypeOf(elems)) {
return @call(.{ .modifier = .always_inline }, TupleReturn(@TypeOf(elems)).init, elems);
}
fn invalidTupleArgType(comptime invalid_type: []const u8, comptime pos: usize) void {
comptime {
var buf: [4096]u8 = undefined;
@compileError(std.fmt.bufPrint(&buf, "tuple called with {} in position {}", .{ invalid_type, i }) catch unreachable);
}
}
fn TupleReturn(comptime ElemsType: type) type {
comptime {
const elems_tinfo = @typeInfo(ElemsType);
if (elems_tinfo != .Struct) @compileError("tuple expect an anonymous struct argument");
const fields = elems_tinfo.Struct.fields;
var field_types: [5]type = undefined;
for (fields) |field, i| {
switch (@typeInfo(field.field_type)) {
.ComptimeInt => invalidTupleArgType("comptime_int", i),
.ComptimeFloat => invalidTupleArgType("comptime_float", i),
.EnumLiteral => invalidTupleArgType("enum literal", i),
.Null => invalidTupleArgType("null", i),
.Undefined => invalidTupleArgType("undefined", i),
.Void => invalidTupleArgType("void", i),
.NoReturn => invalidTupleArgType("noreturn", i),
else => field_types[i] = comptime_utils.GeneralizedType(field.field_type),
}
}
// return Tuple(field_types[0..fields.len]); // compiler bug
return switch (fields.len) {
0 => Tuple0,
1 => Tuple1(field_types[0]),
2 => Tuple2(field_types[0], field_types[1]),
3 => Tuple3(field_types[0], field_types[1], field_types[2]),
4 => Tuple4(field_types[0], field_types[1], field_types[2], field_types[3]),
5 => Tuple5(field_types[0], field_types[1], field_types[2], field_types[3], field_types[4]),
else => @compileError("Tuples above length 5 not supported"),
};
}
}
const TupleTypeMarker = @OpaqueType();
pub fn isATuple(maybe_a_tuple: var) bool {
comptime {
const MaybeATuple = switch (@TypeOf(maybe_a_tuple)) {
type => maybe_a_tuple,
else => @TypeOf(maybe_a_tuple),
};
if (!@hasDecl(MaybeATuple, "ThisIsATuple")) return false;
if (@TypeOf(MaybeATuple.ThisIsATuple) != type) return false;
return MaybeATuple.ThisIsATuple == TupleTypeMarker;
}
}
fn unpackCheck(comptime args_type: type, comptime types: var) void {
comptime {
var buf: [4096]u8 = undefined;
const args_tinfo = @typeInfo(args_type);
if (args_tinfo != .Struct) {
@compileError(std.fmt.bufPrint(&buf, "Tuple.unpack expects an anonymous struct parameter, got a {}", .{@tagName(args_tinfo)}) catch unreachable);
}
const fields = args_tinfo.Struct.fields;
if (fields.len > types.len) {
@compileError(std.fmt.bufPrint(&buf, "Too many arguments passed to Tuple.unpack. Expected: {} Found: {}", .{ types.len, fields.len }) catch unreachable);
}
if (fields.len < types.len) {
@compileError(std.fmt.bufPrint(&buf, "Too few arguments passed to Tuple.unpack. Expected: {} Found: {}", .{ types.len, fields.len }) catch unreachable);
}
for (fields) |_, i| {
if (@typeInfo(fields[i].field_type) == .Null) continue;
if (fields[i].field_type != *types[i]) {
@compileError(std.fmt.bufPrint(&buf, "Incorrect argument type passed to Tuple.unpack. Arg index: {} Expected: {} Found: {}", .{ i, @typeName(*types[i]), @typeName(fields[i].field_type) }) catch unreachable);
}
}
}
}
pub const Tuple0 = struct {
const ThisIsATuple = TupleTypeMarker;
pub const types = [_]type{};
pub fn init() @This() {
return .{};
}
pub fn unpack(self: @This(), vars: var) void {
comptime unpackCheck(@TypeOf(vars), .{});
}
pub fn set(self: *@This(), comptime index: usize, value: var) void {
comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index});
}
pub fn GetReturn(comptime index: usize) type {
comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index});
return undefined;
}
pub fn get(self: *@This(), comptime index: usize) GetReturn(index) {
return undefined;
}
};
pub fn Tuple1(comptime T0: type) type {
return struct {
const ThisIsATuple = TupleTypeMarker;
pub const types = [_]type{T0};
@"0": T0,
pub fn init(x0: T0) @This() {
return .{ .@"0" = x0 };
}
pub fn unpack(self: @This(), vars: var) void {
comptime unpackCheck(@TypeOf(vars), .{ T0, T1 });
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0";
}
pub fn set(self: *@This(), comptime index: usize, value: var) void {
comptime {
if (@TypeOf(value) != types[index]) {
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) });
}
}
switch (comptime index) {
0 => self.@"0" = value,
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple1: {}", .{index}),
}
}
pub fn GetReturn(comptime index: usize) type {
comptime return switch (index) {
0 => T0,
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}),
};
}
pub fn get(self: *@This(), comptime index: usize) GetReturn(index) {
return switch (comptime index) {
0 => self.@"0",
else => undefined,
};
}
};
}
pub fn Tuple2(comptime T0: type, comptime T1: type) type {
return struct {
const ThisIsATuple = TupleTypeMarker;
pub const types = [_]type{ T0, T1 };
@"0": T0,
@"1": T1,
pub fn init(x0: T0, x1: T1) @This() {
return .{ .@"0" = x0, .@"1" = x1 };
}
pub fn unpack(self: @This(), vars: var) void {
comptime unpackCheck(@TypeOf(vars), .{ T0, T1 });
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0";
if (comptime @typeInfo(@TypeOf(vars[1])) != .Null) vars[1].* = self.@"1";
}
pub fn set(self: *@This(), comptime index: usize, value: var) void {
comptime {
if (@TypeOf(value) != types[index]) {
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) });
}
}
switch (comptime index) {
0 => self.@"0" = value,
1 => self.@"1" = value,
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple2: {}", .{index}),
}
}
pub fn GetReturn(comptime index: usize) type {
comptime return switch (index) {
0 => T0,
1 => T1,
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}),
};
}
pub fn get(self: *@This(), comptime index: usize) GetReturn(index) {
return switch (comptime index) {
0 => self.@"0",
1 => self.@"1",
else => undefined,
};
}
};
}
pub fn Tuple3(comptime T0: type, comptime T1: type, comptime T2: type) type {
return struct {
const ThisIsATuple = TupleTypeMarker;
pub const types = [_]type{ T0, T1, T2 };
@"0": T0,
@"1": T1,
@"2": T2,
pub fn init(x0: T0, x1: T1, x2: T2) @This() {
return .{ .@"0" = x0, .@"1" = x1, .@"2" = x2 };
}
pub fn unpack(self: @This(), vars: var) void {
comptime unpackCheck(@TypeOf(vars), .{ T0, T1, T2 });
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0";
if (comptime @typeInfo(@TypeOf(vars[1])) != .Null) vars[1].* = self.@"1";
if (comptime @typeInfo(@TypeOf(vars[2])) != .Null) vars[2].* = self.@"2";
}
pub fn set(self: *@This(), comptime index: usize, value: var) void {
comptime {
if (@TypeOf(value) != types[index]) {
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) });
}
}
switch (comptime index) {
0 => self.@"0" = value,
1 => self.@"1" = value,
2 => self.@"2" = value,
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple3: {}", .{index}),
}
}
pub fn GetReturn(comptime index: usize) type {
comptime return switch (index) {
0 => T0,
1 => T1,
2 => T2,
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}),
};
}
pub fn get(self: *@This(), comptime index: usize) GetReturn(index) {
return switch (comptime index) {
0 => self.@"0",
1 => self.@"1",
2 => self.@"2",
else => undefined,
};
}
};
}
pub fn Tuple4(comptime T0: type, comptime T1: type, comptime T2: type, comptime T3: type) type {
return struct {
const ThisIsATuple = TupleTypeMarker;
pub const types = [_]type{ T0, T1, T2, T3 };
@"0": T0,
@"1": T1,
@"2": T2,
@"3": T3,
pub fn init(x0: T0, x1: T1, x2: T2, x3: T3) @This() {
return .{ .@"0" = x0, .@"1" = x1, .@"2" = x2, .@"3" = x3 };
}
pub fn unpack(self: @This(), vars: var) void {
comptime unpackCheck(@TypeOf(vars), .{ T0, T1, T2, T3 });
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0";
if (comptime @typeInfo(@TypeOf(vars[1])) != .Null) vars[1].* = self.@"1";
if (comptime @typeInfo(@TypeOf(vars[2])) != .Null) vars[2].* = self.@"2";
if (comptime @typeInfo(@TypeOf(vars[3])) != .Null) vars[3].* = self.@"3";
}
pub fn set(self: *@This(), comptime index: usize, value: var) void {
comptime {
if (@TypeOf(value) != types[index]) {
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) });
}
}
switch (comptime index) {
0 => self.@"0" = value,
1 => self.@"1" = value,
2 => self.@"2" = value,
3 => self.@"3" = value,
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple4: {}", .{index}),
}
}
pub fn GetReturn(comptime index: usize) type {
comptime return switch (index) {
0 => T0,
1 => T1,
2 => T2,
3 => T3,
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}),
};
}
pub fn get(self: *@This(), comptime index: usize) GetReturn(index) {
return switch (comptime index) {
0 => self.@"0",
1 => self.@"1",
2 => self.@"2",
3 => self.@"3",
else => undefined,
};
}
};
}
pub fn Tuple5(comptime T0: type, comptime T1: type, comptime T2: type, comptime T3: type, comptime T4: type) type {
return struct {
const ThisIsATuple = TupleTypeMarker;
pub const types = [_]type{ T0, T1, T2, T3, T4 };
@"0": T0,
@"1": T1,
@"2": T2,
@"3": T3,
@"4": T4,
pub fn init(x0: T0, x1: T1, x2: T2, x3: T3, x4: T4) @This() {
return .{ .@"0" = x0, .@"1" = x1, .@"2" = x2, .@"3" = x3, .@"4" = x4 };
}
pub fn unpack(self: @This(), vars: var) void {
comptime unpackCheck(vars, .{ T0, T1, T2, T3, T4 });
if (comptime @typeInfo(@TypeOf(vars[0])) != .Null) vars[0].* = self.@"0";
if (comptime @typeInfo(@TypeOf(vars[1])) != .Null) vars[1].* = self.@"1";
if (comptime @typeInfo(@TypeOf(vars[2])) != .Null) vars[2].* = self.@"2";
if (comptime @typeInfo(@TypeOf(vars[3])) != .Null) vars[3].* = self.@"3";
if (comptime @typeInfo(@TypeOf(vars[4])) != .Null) vars[4].* = self.@"4";
}
pub fn set(self: *@This(), comptime index: usize, value: var) void {
comptime {
if (@TypeOf(value) != types[index]) {
comptime_utils.compileErrorFmt("Invalid type for index {}: {}", .{ index, @typeName(@TypeOf(value)) });
}
}
switch (comptime index) {
0 => self.@"0" = value,
1 => self.@"1" = value,
2 => self.@"2" = value,
3 => self.@"3" = value,
4 => self.@"4" = value,
else => comptime comptime_utils.compileErrorFmt("Invalid index for Tuple5: {}", .{index}),
}
}
pub fn GetReturn(comptime index: usize) type {
comptime return switch (index) {
0 => T0,
1 => T1,
2 => T2,
3 => T3,
4 => T4,
else => comptime_utils.compileErrorFmt("Invalid index for Tuple0: {}", .{index}),
};
}
pub fn get(self: *@This(), comptime index: usize) GetReturn(index) {
return switch (comptime index) {
0 => self.@"0",
1 => self.@"1",
2 => self.@"2",
3 => self.@"3",
4 => self.@"4",
else => undefined,
};
}
};
}
test "Tuple.unpack" {
const x = Tuple(.{ []const u8, []const u8, []const u8 }).init("foo", "bar", "baz");
var a: []const u8 = undefined;
var b: []const u8 = "unchanged";
var c: []const u8 = undefined;
x.unpack(.{ &a, null, &c });
expectEqual(a, "foo");
expectEqual(b, "unchanged");
expectEqual(c, "baz");
}
test "tuple" {
const x: u8 = 0;
expectEqual(Tuple1(u8), @TypeOf(tuple(.{x})));
expectEqual(Tuple1([]const u8), @TypeOf(tuple(.{"foo"})));
expectEqual(Tuple2(u8, u8), @TypeOf(tuple(.{ x, x })));
expectEqual(Tuple3(u8, u8, u8), @TypeOf(tuple(.{ x, x, x })));
expectEqual(Tuple4(u8, u8, u8, u8), @TypeOf(tuple(.{ x, x, x, x })));
expectEqual(Tuple5(u8, u8, u8, u8, u8), @TypeOf(tuple(.{ x, x, x, x, x })));
}
test "Tuple" {
const x: u8 = 0;
expectEqual(Tuple1(u8), Tuple(.{u8}));
expectEqual(Tuple2(u8, u8), Tuple(.{ u8, u8 }));
expectEqual(Tuple3(u8, u8, u8), Tuple(.{ u8, u8, u8 }));
expectEqual(Tuple4(u8, u8, u8, u8), Tuple(.{ u8, u8, u8, u8 }));
expectEqual(Tuple5(u8, u8, u8, u8, u8), Tuple(.{ u8, u8, u8, u8, u8 }));
}
test "Tuple.set" {
const baz: []const u8 = "baz";
const four_twenty = @as(u16, 420);
var x = tuple(.{ "foo", "bar", @as(u16, 69) });
inline for (@TypeOf(x).types) |T, i| {
x.set(i, switch (T) {
[]const u8 => baz,
u16 => four_twenty,
else => unreachable,
});
}
expectEqual(tuple(.{ "baz", "baz", @as(u16, 420) }), x);
}
test "Tuple.set" {
const foo: []const u8 = "foo";
const bar: []const u8 = "bar";
var x = tuple(.{ "foo", "bar", @as(u16, 69) });
inline for (@TypeOf(x).types) |T, i| {
switch (i) {
0 => expectEqual(foo, x.get(i)),
1 => expectEqual(bar, x.get(i)),
2 => expectEqual(@as(u16, 69), x.get(i)),
else => unreachable,
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment