Created
June 21, 2020 20:57
-
-
Save adrusi/54ed2be2fbc6e9fb0c68f3c6f8706f9b to your computer and use it in GitHub Desktop.
Tuples in Zig 0.6.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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