CrepeGoat
CrepeGoat

Reputation: 2515

encountered `zig compiler bug: GenericPoison`?

Platform details

Problem

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

Questions

  1. Any ideas on what the problem is with the code? Edit: Or is this an issue with the compiler?
  2. 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

Answers (1)

pfg
pfg

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:

  1. (causing the GenericPoison compiler bug): 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) boolpredicate: anytype fixes this issue

After 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)

  1. @typeInfo(predicate) - predicate is of type fn(u32) bool here, so you need to use @TypeOf: @typeInfo(@TypeOf(predicate))

  2. .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.?

  3. 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.

  4. 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:

  • Code that should not compile, but the error is not caught and displayed correctly - this is a bug in the compiler
  • Code that should compile, but there is a bug in the compiler preventing it from compiling

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

Related Questions