Reputation: 2515
I wrote this code for basic composable iterators:
pub fn RangeIterator(comptime NumberType: type) type {
return struct {
const This = @This();
const Item = NumberType;
start: NumberType,
step: NumberType,
len: usize,
_index: usize = 0,
fn next(this: *This) !?Item {
if (this._index >= this.len) {
return null;
}
defer this._index += 1;
return this.start + (this.step * @intCast(NumberType, this._index));
}
};
}
test "range iterator" {
// these tests pass!
var iterator = RangeIterator(u32){.start = 1, .step = 3, .len = 5};
try std.testing.expectEqual(iterator.next(), 1);
try std.testing.expectEqual(iterator.next(), 4);
try std.testing.expectEqual(iterator.next(), 7);
try std.testing.expectEqual(iterator.next(), 10);
try std.testing.expectEqual(iterator.next(), 13);
try std.testing.expectEqual(iterator.next(), null);
}
// based on pattern used for BufferedWriter & bufferedWriter
// https://github.com/ziglang/zig/blob/master/lib/std/io/buffered_writer.zig
pub fn IterWhile(
comptime IteratorType: type,
comptime ItemType: type,
comptime include_last: bool,
) type {
return struct {
const This = @This();
const Item = ItemType;
iterator: IteratorType,
predicate: fn (ItemType) bool,
_is_spent: bool = false,
pub fn next(this: *This) ?ItemType {
if (this._is_spent) {
return null;
}
const result = try this.iterator.next();
if (result == null) {
return null;
}
if (!this.predicate(result)) {
this._is_spent = true;
if (!include_last) return null;
}
return result;
}
};
}
pub fn iterWhile(
iterator: anytype,
predicate: fn (anytype) bool,
comptime include_last: bool,
) IterWhile(
@TypeOf(iterator),
@typeInfo(predicate).Fn.args[0].arg_type,
include_last,
) {
return .{ .iterator = iterator, .predicate = predicate };
}
test "iter while" {
// these tests generate a compiler error :S
var range = RangeIterator(u32){.start = 1, .step = 3, .len = 5};
var iterator = iterWhile(range, TestUtils.isOneDigit, false);
try std.testing.expectEqual(iterator.next(), 1);
try std.testing.expectEqual(iterator.next(), 4);
try std.testing.expectEqual(iterator.next(), 7);
try std.testing.expectEqual(iterator.next(), null);
}
const TestUtils = struct {
fn isOneDigit(value: u32) bool {
return value < 10;
}
};
If I comment out the second test block "iter while"
, the tests pass. But if I leave them in, I get the following error:
thread 1531026 panic: zig compiler bug: GenericPoison
Unable to dump stack trace: debug info stripped
zsh: abort zig test src/utils/iterator.zig
debug info stripped
-> why is that happening? Aren't I running this in debug mode by not passing any of the release build flags?Upvotes: 0
Views: 160
Reputation: 3577
While the error message is due to a bug in the compiler, there are some bugs in your code that when resolved allow the code to compile and run as expected:
fn(u32) bool
is not assignable to fn(anytype) bool
. anytype
is a special value that can be used in arguments to specify that the argument should be generic and make a new copy of the function for every different type passed in. Changing predicate: fn(anytype) bool
→ predicate: anytype
fixes this issueAfter this, the rest of the issues have proper error messages (although some of the next ones will not display in color and note that there is a zig compiler bug related to their display: Zig compiler bug: attempted to destroy declaration with an attached error
)
@typeInfo(predicate)
- predicate is of type fn(u32) bool
here, so you need to use @TypeOf
: @typeInfo(@TypeOf(predicate))
.arg_type
- this is an optional. a null value means that the argument type is set to anytype
. Use .arg_type.?
here. Note that in newer versions of zig, this is changed to .Fn.params[0].type.?
predicate: fn(ItemType) bool
- this is causing the struct to be comptime-only. To make it available at runtime, use a function pointer: *const fn(ItemType) bool
and .predicate = &predicate
. The error message on this one is not very helpful - it says the function is being called at comptime because its return type is comptime-only, but does not explain why that is.
this.predicate(result)
- here result is an optional. since you tested for null above, you can safely use result.?
here or you can change the code so result is no longer an optional with orelse
:
const result = (try this.iterator.next()) orelse return null;
Now the tests will pass
Info about the compiler bug:
thread 1531026 panic: zig compiler bug: GenericPoison
Unable to dump stack trace: debug info stripped
zsh: abort zig test src/utils/iterator.zig
zig compiler bug
This message indicates that the error is a bug in the zig compiler; you should never see it. A compiler bug can either indicate:
Here, the code should cause a compile error about fn(u32) bool
not being assignable to fn(anytype) bool
, but there is a bug in the compiler somewhere causing it to say GenericPoison
instead
Expected error message:
a.zig:xx:xx: error: expected type 'fn(anytype) bool', found 'fn(u32) bool'
Minimal repro:
const std = @import("std");
pub fn iterWhile(
predicate: fn (anytype) bool,
) void {
_ = predicate;
}
test "iter while" {
iterWhile(isOneDigit);
}
fn isOneDigit(value: u32) bool {
return value < 10;
}
Issue report: #14052, maybe related to the comment on #12373
Upvotes: 1