Can't work around the "error: unable to resolve inferred error set"

I’m trying to implement a simple pretty printer for trees of arbitrary types and stuck at the point where the code below:

const std = @import("std");

fn ext(comptime path: []const u8) []const u8 {
    var start = path.len -| 1;
    while (start != 0) : (start -|= 1)
        if (path[start] == '.') break;
    return path[start..];
}

pub fn prettyPrint(alloc: std.mem.Allocator, val: anytype) !void {
    const String = std.ArrayList(u8);
    const Printer = struct {
        lvl: usize = 0,
        alloc: std.mem.Allocator,
        out: String,

        const Self = @This();
        const Error = error{ValueTypeNotSupported};

        pub fn init(a: std.mem.Allocator) Self {
            return Self{ .out = String.init(a), .alloc = a };
        }

        pub fn run(self: *Self, next: anytype, cont: bool, field_name: ?[]const u8) !void {
            const val_T = @TypeOf(next);
            const val_T_info = @typeInfo(val_T);
            const val_T_tag = @tagName(val_T_info);
            const val_T_name = @typeName(val_T);

            var buf: [256]u8 = undefined;
            var printed: []const u8 = undefined;

            switch (val_T_info) {
                .Pointer => {
                    try self.out.append('*');
                    try self.run(next.*, true, null);
                    return;
                },
                .Struct => {
                    if (!cont) try self.out.appendNTimes(' ', self.lvl * 2);
                    try self.out.appendSlice("." ++ val_T_tag ++ comptime ext(val_T_name));
                    if (field_name) |name| {
                        // try self.out.appendSlice("(");
                        try self.out.appendSlice(name);
                        try self.out.appendSlice(": ");
                    }
                    try self.out.append('\n');
                    self.lvl += 1;
                    inline for (std.meta.fields(val_T)) |field| {
                        try self.out.appendNTimes(' ', self.lvl * 2);
                        try self.out.appendSlice("" ++ field.name ++ ": ");
                        try self.run(@field(next, field.name), true, null);
                    }
                },
                .Int, .ComptimeInt => {
                    if (!cont) try self.out.appendNTimes(' ', self.lvl * 2);
                    if (field_name) |name| {
                        // try self.out.appendSlice("(");
                        try self.out.appendSlice(name);
                        try self.out.appendSlice(": ");
                    }
                    try self.out.appendSlice("." ++ val_T_tag ++ "." ++ comptime ext(val_T_name));
                    try self.out.append('\n');
                    try self.out.appendNTimes(' ', (self.lvl + 1) * 2);
                    printed = try std.fmt.bufPrint(&buf, "{any}", .{next});
                    try self.out.appendSlice(printed);
                    try self.out.append('\n');
                },
                else => {
                    self.out.clearAndFree();
                    try self.out.appendSlice("Error.ValueTypeNotSupported");
                    // return Error.ValueTypeNotSupported;
                },
            }
        }

        pub fn deinit(self: *Self) void {
            self.out.deinit();
        }
    };

    var p = Printer.init(alloc);
    try p.run(val, false, null);
    defer p.deinit();

    std.debug.print("{s}\n", .{p.out.items});
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const alloc = gpa.allocator();

    const Pet = struct { Age: u8, Name: u8, Next: *const @This() };
    const puppy1 = Pet{ .Age = 1, .Name = 2, .Next = undefined };

    // Start here
    try prettyPrint(alloc, &puppy1);
}

Throws:

.notes/q_unableToResolveError.zig:52:37: error: unable to resolve inferred error set
                        try self.run(@field(next, field.name), true, null);

It feels like it is somehow related to recursion. It happened once before already but somehow I was able to get around it. After I added a new .Next field to the Pet, I started to get that error again. Complete MIA.

This usually means you cannot use an implied error set for your function declaration:

pub fn foo() !void { ... }

but instead have to declare an error set explicitly:

const DwarfError = error { bifur, bofur, bombur };
pub fn foo() DwarfError!void { ... }

And yes, recursive functions usually exhibit this behaviour.

Unfortunately, it doesn’t help. That’s why in the code above I commented out any use of errors of my own. Trying to put every error enum in the return type explicitly:

pub fn run(self: *Self, next: anytype, cont: bool, field_name: ?[]const u8) std.fmt.BufPrintError!std.mem.Allocator.Error!void { ...

Gives the whole bunch of new weird stuff:

.notes/q_unableToResolveError.zig:83:5: error: error is ignored
    try p.run(val, false, null);
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
.notes/q_unableToResolveError.zig:83:5: note: consider using 'try', 'catch', or 'if'
referenced by:
    main: .notes/q_unableToResolveError.zig:98:20
    callMain: /Users/timfayz/.zig/lib/std/start.zig:585:32
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
.notes/q_unableToResolveError.zig:35:21: error: expected type 'error{NoSpaceLeft}', found 'error{OutOfMemory}'
                    try self.out.append('*');
                    ^~~~~~~~~~~~~~~~~~~~~~~~
.notes/q_unableToResolveError.zig:35:21: note: 'error.OutOfMemory' not a member of destination error set                                                                                                             

That is not the right way to combine error sets. That’s more like writing !!void. That’s why it’s not working correctly here. (If you actually wanted to use this you’d need to use try try to unwrap both errors)
I think you can combine error sets with the || operator:

(std.fmt.BufPrintError || std.mem.Allocator.Error)!void
3 Likes

Feel stupid and thanks a lot! :slight_smile: Am I right that since I’m using recursion and Zig is work in progress for inferring error types for recursive functions (#2971), this union is the right way to go? (for now)

1 Like

The union, or an explicit error set.

Or if one really feels lazy then anyerror!void. It catches all errors.

EDIT: corrected typo error => anyerror

1 Like

That’s what I actually wanted! :blush: Thanks a lot for the hint!
Just in case, it is anyerror!void to be compiled (anyerror).