Skip to content

Instantly share code, notes, and snippets.

@adrusi
Last active June 21, 2020 23:12
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/f16edf3930a2973ce2d546f2acf23e9b to your computer and use it in GitHub Desktop.
Save adrusi/f16edf3930a2973ce2d546f2acf23e9b to your computer and use it in GitHub Desktop.
Async generators in Zig 0.6.0
const std = @import("std");
const expectEqual = std.testing.expectEqual;
const expect = std.testing.expect;
const tuples = @import("tuples.zig");
const Tuple = tuples.Tuple;
const tuple = tuples.tuple;
const comptime_utils = @import("comptime_utils.zig");
pub fn Generator(comptime Ctx: type) type {
comptime {
const ctx_tinfo = @typeInfo(Ctx);
if (ctx_tinfo != .Struct) {
@compileError("Generator specification must be a struct");
}
if (!@hasDecl(Ctx, "generate")) {
@compileError("Generator specification must have a 'generate' declaration");
}
const gen_fn = Ctx.generate;
const fn_tinfo = @typeInfo(@TypeOf(gen_fn));
if (fn_tinfo != .Fn) {
@compileError("Generator specification: 'generate' must be a function");
}
if (fn_tinfo.Fn.args.len != 2) {
@compileError("Generator specification: 'generate' must accept exactly two parameters");
}
const arg1_tinfo = @typeInfo(fn_tinfo.Fn.args[0].arg_type.?);
const arg2_tinfo = @typeInfo(fn_tinfo.Fn.args[1].arg_type.?);
const ret_tinfo = @typeInfo(fn_tinfo.Fn.return_type.?);
if (arg1_tinfo != .Pointer) {
@compileError("Generator specification: first parameter of 'generate' must be a pointer");
}
if (arg1_tinfo.Pointer.child != Ctx) {
@compileError("Generator specification: first parameter of 'generate' must be a pointer to the generator specification struct");
}
if (ret_tinfo != .ErrorUnion) {
@compileError("Generator specification: 'generate' must return an error union with void payload");
}
if (ret_tinfo.ErrorUnion.payload != void) {
@compileError("Generator specification: 'generate' must return an error union with void payload");
}
if (arg2_tinfo != .Pointer) {
@compileError("Generator specification: second parameter of 'generate' must be a pointer");
}
const arg2_child = arg2_tinfo.Pointer.child;
if (@typeInfo(arg2_child) != .Union) {
@compileError("Generator specification: second parameter of 'generate' must be a pointer to a Scheduler");
}
if (!@hasDecl(arg2_child, "ThisIsAScheduler") or arg2_child.ThisIsAScheduler != SchedulerTypeMarker) {
@compileError("Generator specification: second parameter of 'generate' must be a pointer to a Scheduler");
}
const Awaitables = arg2_child.Awaitables;
return struct {
pub const Args = Ctx;
pub const Error = ret_tinfo.ErrorUnion.error_set;
pub const Item = arg2_child.Item;
state: enum { start, alive, end, post_end },
err: ?Error,
gen_frame: @Frame(gen_fn),
awaiter_frame: @Frame(returnValueAwaiter),
ctx: Ctx,
Scheduler: Scheduler(Item, Awaitables),
pub fn init(ctx: Ctx) @This() {
return .{
.state = .start,
.err = null,
.gen_frame = undefined,
.awaiter_frame = undefined,
.ctx = ctx,
.Scheduler = undefined,
};
}
pub fn next(self: *@This()) Error!?Item {
switch (self.state) {
.start => self.awaiter_frame = async self.returnValueAwaiter(),
.alive => resume self.gen_frame,
.end => {
nosuspend await self.awaiter_frame;
self.state = .post_end;
if (self.err) |err| return err;
return null;
},
.post_end => return null,
}
// generate has suspended
while (true) {
// await all frames that generate calls Scheduler.await_ on until generate yields a value.
switch (self.Scheduler) {
.yielded => |item| return item,
.awaited => |awaitspec| {
inline for (Awaitables.types) |_, i| {
if (awaitspec.result.get(i)) |result| result.* = await awaitspec.frame.get(i);
}
resume awaitspec.frame_to_resume;
},
}
}
}
fn returnValueAwaiter(self: *@This()) void {
self.state = .alive;
defer self.state = .end;
self.gen_frame = async gen_fn(&self.ctx, &self.Scheduler);
await self.gen_frame catch |err| {
self.err = err;
};
}
};
}
}
const SchedulerTypeMarker = @OpaqueType();
pub fn Scheduler(comptime _Item: type, comptime _Awaitables: type) type {
comptime {
var Frames: type = undefined;
var Results: type = undefined;
var frame_types: [_Awaitables.types.len]type = undefined;
for (_Awaitables.types) |AwaitableType, i| {
frame_types[i] = anyframe->AwaitableType;
}
// compiler bug workaround
Results = switch (_Awaitables.types.len) {
0 => tuples.Tuple0(),
1 => tuples.Tuple1(?*_Awaitables.types[0]),
2 => tuples.Tuple2(?*_Awaitables.types[0], ?*_Awaitables.types[1]),
3 => tuples.Tuple3(?*_Awaitables.types[0], ?*_Awaitables.types[1], ?*_Awaitables.types[2]),
4 => tuples.Tuple4(?*_Awaitables.types[0], ?*_Awaitables.types[1], ?*_Awaitables.types[2], ?*_Awaitables.types[3]),
5 => tuples.Tuple5(?*_Awaitables.types[0], ?*_Awaitables.types[1], ?*_Awaitables.types[2], ?*_Awaitables.types[3], ?*_Awaitables.types[4]),
else => @compileError("Scheduler: cannot declare more than 5 awaitable types"),
};
Frames = Tuple(frame_types);
var empty_results: Results = undefined;
inline for (Results.types) |ResultType, i| {
empty_results.set(i, @as(ResultType, null));
}
return union(enum) {
const ThisIsAScheduler = SchedulerTypeMarker;
const Item = _Item;
const Awaitables = _Awaitables;
yielded: Item,
awaited: struct { result: Results, frame: Frames, frame_to_resume: anyframe },
pub fn yield(self: *@This(), item: Item) void {
suspend {
self.* = .{ .yielded = item };
}
}
fn await_Aux(comptime Frame: type) struct { Return: type, index: usize } {
comptime {
var Result: type = undefined;
var awaitable_index: usize = undefined;
const frame_tinfo = @typeInfo(Frame);
if (frame_tinfo != .AnyFrame or frame_tinfo.AnyFrame.child == null) {
comptime_utils.compileErrorFmt("Scheduler.await_: expected anyframe->? in first argument, got: {}", .{@typeName(Frame)});
}
Result = frame_tinfo.AnyFrame.child.?;
var valid_result_type = false;
for (Awaitables.types) |Awaitable, i| {
if (Result == Awaitable) {
valid_result_type = true;
awaitable_index = i;
break;
}
}
if (!valid_result_type) {
comptime_utils.compileErrorFmt("Must declare awaited type to Scheduler: {}", .{@typeName(Result)});
}
return .{ .Return = Result, .index = awaitable_index };
}
}
pub fn await_(self: *@This(), frame: var) await_Aux(@TypeOf(frame)).Return {
comptime const type_info = await_Aux(@TypeOf(frame));
var result: type_info.Return = undefined;
suspend {
self.* = .{
.awaited = .{
.result = empty_results,
.frame = undefined,
.frame_to_resume = @frame(),
},
};
const opt_result_ptr: ?*type_info.Return = &result;
const frame_as_anyframe: anyframe->type_info.Return = frame;
self.awaited.result.set(type_info.index, opt_result_ptr);
self.awaited.frame.set(type_info.index, frame_as_anyframe);
}
return result;
}
};
}
}
var global_frame: ?anyframe = null;
fn get5() u64 {
suspend {
global_frame = @frame();
}
return 5;
}
const FiveIter = Generator(struct {
pub fn generate(_: *@This(), scheduler: *Scheduler(u64, Tuple(.{u64}))) !void {
const five = scheduler.await_(@as(anyframe->u64, &async get5()));
scheduler.yield(five);
}
});
test "Async generators" {
var iter = FiveIter.init(.{});
var next_frame = async iter.next();
expect(global_frame != null);
resume global_frame.?;
expectEqual((try await next_frame).?, 5);
}
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");
}));
}
}
// TODO refactor to work like https://godbolt.org/z/9Q5aE-
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: *const @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: *const @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: *const @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: *const @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: *const @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: *const @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([_]type)" {
comptime const raw_types = [_]type{ u8, u16, u32 };
comptime var types: [raw_types.len]type = undefined;
comptime {
inline for (raw_types) |raw_type, i| {
types[i] = ?*raw_type;
}
}
const built_tuple = Tuple(types).init(undefined, undefined, 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