Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

ErrorDetails

errors.ErrorDetails
pub const ErrorDetails = struct

File

Code

pub const ErrorDetails = struct {
    err: Error,
    token: Token,
    /// Code page of the source file at the token location
    code_page: SupportedCodePage,
    /// If non-null, should be before `token`. If null, `token` is assumed to be the start.
    token_span_start: ?Token = null,
    /// If non-null, should be after `token`. If null, `token` is assumed to be the end.
    token_span_end: ?Token = null,
    type: Type = .err,
    print_source_line: bool = true,
    extra: Extra = .{ .none = {} },

    pub const Type = enum {
        /// Fatal error, stops compilation
        err,
        /// Warning that does not affect compilation result
        warning,
        /// A note that typically provides further context for a warning/error
        note,
        /// An invisible diagnostic that is not printed to stderr but can
        /// provide information useful when comparing the behavior of different
        /// implementations. For example, a hint is emitted when a FONTDIR resource
        /// was included in the .RES file which is significant because rc.exe
        /// does something different than us, but ultimately it's not important
        /// enough to be a warning/note.
        hint,
    };

    pub const Extra = union {
        none: void,
        expected: Token.Id,
        number: u32,
        expected_types: ExpectedTypes,
        resource: rc.ResourceType,
        string_and_language: StringAndLanguage,
        file_open_error: FileOpenError,
        icon_read_error: IconReadError,
        icon_dir: IconDirContext,
        bmp_read_error: BitmapReadError,
        accelerator_error: AcceleratorError,
        statement_with_u16_param: StatementWithU16Param,
        menu_or_class: enum { class, menu },
    };

    comptime {
        // all fields in the extra union should be 32 bits or less
        for (std.meta.fieldTypes(Extra)) |field_type| {
            std.debug.assert(@bitSizeOf(field_type) <= 32);
        }
    }

    pub const StatementWithU16Param = enum(u32) {
        fileversion,
        productversion,
        language,
    };

    pub const StringAndLanguage = packed struct(u32) {
        id: u16,
        language: res.Language,
    };

    pub const FileOpenError = packed struct(u32) {
        err: FileOpenErrorEnum,
        filename_string_index: FilenameStringIndex,

        pub const FilenameStringIndex = @Int(.unsigned, 32 - @bitSizeOf(FileOpenErrorEnum));
        pub const FileOpenErrorEnum = std.meta.FieldEnum(Io.File.OpenError || Io.File.StatError);

        pub fn enumFromError(err: (Io.File.OpenError || Io.File.StatError)) FileOpenErrorEnum {
            return switch (err) {
                inline else => |e| @field(ErrorDetails.FileOpenError.FileOpenErrorEnum, @errorName(e)),
            };
        }
    };

    pub const IconReadError = packed struct(u32) {
        err: IconReadErrorEnum,
        icon_type: enum(u1) { cursor, icon },
        filename_string_index: FilenameStringIndex,

        pub const FilenameStringIndex = @Int(.unsigned, 32 - @bitSizeOf(IconReadErrorEnum) - 1);
        pub const IconReadErrorEnum = std.meta.FieldEnum(ico.ReadError);

        pub fn enumFromError(err: ico.ReadError) IconReadErrorEnum {
            return switch (err) {
                inline else => |e| @field(ErrorDetails.IconReadError.IconReadErrorEnum, @errorName(e)),
            };
        }
    };

    pub const IconDirContext = packed struct(u32) {
        icon_type: enum(u1) { cursor, icon },
        icon_format: ico.ImageFormat,
        index: u16,
        bitmap_version: ico.BitmapHeader.Version = .unknown,
        _: Padding = 0,

        pub const Padding = @Int(.unsigned, 15 - @bitSizeOf(ico.BitmapHeader.Version) - @bitSizeOf(ico.ImageFormat));
    };

    pub const BitmapReadError = packed struct(u32) {
        err: BitmapReadErrorEnum,
        filename_string_index: FilenameStringIndex,

        pub const FilenameStringIndex = @Int(.unsigned, 32 - @bitSizeOf(BitmapReadErrorEnum));
        pub const BitmapReadErrorEnum = std.meta.FieldEnum(bmp.ReadError);

        pub fn enumFromError(err: bmp.ReadError) BitmapReadErrorEnum {
            return switch (err) {
                inline else => |e| @field(ErrorDetails.BitmapReadError.BitmapReadErrorEnum, @errorName(e)),
            };
        }
    };

    pub const BitmapUnsupportedDIB = packed struct(u32) {
        dib_version: ico.BitmapHeader.Version,
        filename_string_index: FilenameStringIndex,

        pub const FilenameStringIndex = @Int(.unsigned, 32 - @bitSizeOf(ico.BitmapHeader.Version));
    };

    pub const AcceleratorError = packed struct(u32) {
        err: AcceleratorErrorEnum,
        _: Padding = 0,

        pub const Padding = @Int(.unsigned, 32 - @bitSizeOf(AcceleratorErrorEnum));
        pub const AcceleratorErrorEnum = std.meta.FieldEnum(res.ParseAcceleratorKeyStringError);

        pub fn enumFromError(err: res.ParseAcceleratorKeyStringError) AcceleratorErrorEnum {
            return switch (err) {
                inline else => |e| @field(ErrorDetails.AcceleratorError.AcceleratorErrorEnum, @errorName(e)),
            };
        }
    };

    pub const ExpectedTypes = packed struct(u32) {
        number: bool = false,
        number_expression: bool = false,
        string_literal: bool = false,
        accelerator_type_or_option: bool = false,
        control_class: bool = false,
        literal: bool = false,
        // Note: This being 0 instead of undefined is arbitrary and something of a workaround,
        //       see https://github.com/ziglang/zig/issues/15395
        _: u26 = 0,

        pub const strings = std.StaticStringMap([]const u8).initComptime(.{
            .{ "number", "number" },
            .{ "number_expression", "number expression" },
            .{ "string_literal", "quoted string literal" },
            .{ "accelerator_type_or_option", "accelerator type or option [ASCII, VIRTKEY, etc]" },
            .{ "control_class", "control class [BUTTON, EDIT, etc]" },
            .{ "literal", "unquoted literal" },
        });

        pub fn writeCommaSeparated(self: ExpectedTypes, writer: *std.Io.Writer) !void {
            const struct_info = @typeInfo(ExpectedTypes).@"struct";
            const num_real_fields = struct_info.field_names.len - 1;
            const num_padding_bits = @bitSizeOf(ExpectedTypes) - num_real_fields;
            const mask = std.math.maxInt(struct_info.backing_integer.?) >> num_padding_bits;
            const relevant_bits_only = @as(struct_info.backing_integer.?, @bitCast(self)) & mask;
            const num_set_bits = @popCount(relevant_bits_only);

            var i: usize = 0;
            inline for (struct_info.field_names, struct_info.field_types) |field_name, field_type| {
                if (field_type != bool) continue;
                if (i == num_set_bits) return;
                if (@field(self, field_name)) {
                    try writer.writeAll(strings.get(field_name).?);
                    i += 1;
                    if (num_set_bits > 2 and i != num_set_bits) {
                        try writer.writeAll(", ");
                    } else if (i != num_set_bits) {
                        try writer.writeByte(' ');
                    }
                    if (num_set_bits > 1 and i == num_set_bits - 1) {
                        try writer.writeAll("or ");
                    }
                }
            }
        }
    };

    pub const Error = enum {
        // Lexer
        unfinished_string_literal,
        string_literal_too_long,
        invalid_number_with_exponent,
        invalid_digit_character_in_number_literal,
        illegal_byte,
        illegal_byte_outside_string_literals,
        illegal_codepoint_outside_string_literals,
        illegal_byte_order_mark,
        illegal_private_use_character,
        found_c_style_escaped_quote,
        code_page_pragma_missing_left_paren,
        code_page_pragma_missing_right_paren,
        code_page_pragma_invalid_code_page,
        code_page_pragma_not_integer,
        code_page_pragma_overflow,
        code_page_pragma_unsupported_code_page,

        // Parser
        unfinished_raw_data_block,
        unfinished_string_table_block,
        /// `expected` is populated.
        expected_token,
        /// `expected_types` is populated
        expected_something_else,
        /// `resource` is populated
        resource_type_cant_use_raw_data,
        /// `resource` is populated
        id_must_be_ordinal,
        /// `resource` is populated
        name_or_id_not_allowed,
        string_resource_as_numeric_type,
        ascii_character_not_equivalent_to_virtual_key_code,
        empty_menu_not_allowed,
        rc_would_miscompile_version_value_padding,
        rc_would_miscompile_version_value_byte_count,
        code_page_pragma_in_included_file,
        nested_resource_level_exceeds_max,
        too_many_dialog_controls_or_toolbar_buttons,
        nested_expression_level_exceeds_max,
        close_paren_expression,
        unary_plus_expression,
        rc_could_miscompile_control_params,
        dangling_literal_at_eof,
        disjoint_code_page,

        // Compiler
        /// `string_and_language` is populated
        string_already_defined,
        font_id_already_defined,
        /// `file_open_error` is populated
        file_open_error,
        /// `accelerator_error` is populated
        invalid_accelerator_key,
        accelerator_type_required,
        accelerator_shift_or_control_without_virtkey,
        rc_would_miscompile_control_padding,
        rc_would_miscompile_control_class_ordinal,
        /// `icon_dir` is populated
        rc_would_error_on_icon_dir,
        /// `icon_dir` is populated
        format_not_supported_in_icon_dir,
        /// `resource` is populated and contains the expected type
        icon_dir_and_resource_type_mismatch,
        /// `icon_read_error` is populated
        icon_read_error,
        /// `icon_dir` is populated
        rc_would_error_on_bitmap_version,
        /// `icon_dir` is populated
        max_icon_ids_exhausted,
        /// `bmp_read_error` is populated
        bmp_read_error,
        /// `number` is populated and contains a string index for which the string contains
        /// the bytes of a `u64` (native endian). The `u64` contains the number of ignored bytes.
        bmp_ignored_palette_bytes,
        /// `number` is populated and contains a string index for which the string contains
        /// the bytes of a `u64` (native endian). The `u64` contains the number of missing bytes.
        bmp_missing_palette_bytes,
        /// `number` is populated and contains a string index for which the string contains
        /// the bytes of a `u64` (native endian). The `u64` contains the number of miscompiled bytes.
        rc_would_miscompile_bmp_palette_padding,
        resource_header_size_exceeds_max,
        resource_data_size_exceeds_max,
        control_extra_data_size_exceeds_max,
        version_node_size_exceeds_max,
        fontdir_size_exceeds_max,
        /// `number` is populated and contains a string index for the filename
        number_expression_as_filename,
        /// `number` is populated and contains the control ID that is a duplicate
        control_id_already_defined,
        /// `number` is populated and contains the disallowed codepoint
        invalid_filename,
        /// `statement_with_u16_param` is populated
        rc_would_error_u16_with_l_suffix,
        result_contains_fontdir,
        /// `number` is populated and contains the ordinal value that the id would be miscompiled to
        rc_would_miscompile_dialog_menu_id,
        /// `number` is populated and contains the ordinal value that the value would be miscompiled to
        rc_would_miscompile_dialog_class,
        /// `menu_or_class` is populated and contains the type of the parameter statement
        rc_would_miscompile_dialog_menu_or_class_id_forced_ordinal,
        rc_would_miscompile_dialog_menu_id_starts_with_digit,
        dialog_menu_id_was_uppercased,
        duplicate_optional_statement_skipped,
        invalid_digit_character_in_ordinal,

        // Literals
        /// `number` is populated
        rc_would_miscompile_codepoint_whitespace,
        /// `number` is populated
        rc_would_miscompile_codepoint_skip,
        /// `number` is populated
        rc_would_miscompile_codepoint_bom,
        tab_converted_to_spaces,

        // General (used in various places)
        /// `number` is populated and contains the value that the ordinal would have in the Win32 RC compiler implementation
        win32_non_ascii_ordinal,

        // Initialization
        /// `file_open_error` is populated, but `filename_string_index` is not
        failed_to_open_cwd,
    };

    fn formatToken(ctx: TokenFormatContext, writer: *std.Io.Writer) std.Io.Writer.Error!void {
        switch (ctx.token.id) {
            .eof => return writer.writeAll(ctx.token.id.nameForErrorDisplay()),
            else => {},
        }

        const slice = ctx.token.slice(ctx.source);
        var src_i: usize = 0;
        while (src_i < slice.len) {
            const codepoint = ctx.code_page.codepointAt(src_i, slice) orelse break;
            defer src_i += codepoint.byte_len;
            const display_codepoint = codepointForDisplay(codepoint) orelse continue;
            var buf: [4]u8 = undefined;
            const utf8_len = std.unicode.utf8Encode(display_codepoint, &buf) catch unreachable;
            try writer.writeAll(buf[0..utf8_len]);
        }
    }

    const TokenFormatContext = struct {
        token: Token,
        source: []const u8,
        code_page: SupportedCodePage,
    };

    fn fmtToken(self: ErrorDetails, source: []const u8) std.fmt.Alt(TokenFormatContext, formatToken) {
        return .{ .data = .{
            .token = self.token,
            .code_page = self.code_page,
            .source = source,
        } };
    }

    pub fn render(self: ErrorDetails, writer: *std.Io.Writer, source: []const u8, strings: []const []const u8) !void {
        switch (self.err) {
            .unfinished_string_literal => {
                return writer.print("unfinished string literal at '{f}', expected closing '\"'", .{self.fmtToken(source)});
            },
            .string_literal_too_long => {
                return writer.print("string literal too long (max is currently {} characters)", .{self.extra.number});
            },
            .invalid_number_with_exponent => {
                return writer.print("base 10 number literal with exponent is not allowed: {s}", .{self.token.slice(source)});
            },
            .invalid_digit_character_in_number_literal => switch (self.type) {
                .err, .warning => return writer.writeAll("non-ASCII digit characters are not allowed in number literals"),
                .note => return writer.writeAll("the Win32 RC compiler allows non-ASCII digit characters, but will miscompile them"),
                .hint => return,
            },
            .illegal_byte => {
                return writer.print("character '{f}' is not allowed", .{
                    std.ascii.hexEscape(self.token.slice(source), .upper),
                });
            },
            .illegal_byte_outside_string_literals => {
                return writer.print("character '{f}' is not allowed outside of string literals", .{
                    std.ascii.hexEscape(self.token.slice(source), .upper),
                });
            },
            .illegal_codepoint_outside_string_literals => {
                // This is somewhat hacky, but we know that:
                //  - This error is only possible with codepoints outside of the Windows-1252 character range
                //  - So, the only supported code page that could generate this error is UTF-8
                // Therefore, we just assume the token bytes are UTF-8 and decode them to get the illegal
                // codepoint.
                //
                // FIXME: Support other code pages if they become relevant
                const bytes = self.token.slice(source);
                const codepoint = std.unicode.utf8Decode(bytes) catch unreachable;
                return writer.print("codepoint <U+{X:0>4}> is not allowed outside of string literals", .{codepoint});
            },
            .illegal_byte_order_mark => {
                return writer.writeAll("byte order mark <U+FEFF> is not allowed");
            },
            .illegal_private_use_character => {
                return writer.writeAll("private use character <U+E000> is not allowed");
            },
            .found_c_style_escaped_quote => {
                return writer.writeAll("escaping quotes with \\\" is not allowed (use \"\" instead)");
            },
            .code_page_pragma_missing_left_paren => {
                return writer.writeAll("expected left parenthesis after 'code_page' in #pragma code_page");
            },
            .code_page_pragma_missing_right_paren => {
                return writer.writeAll("expected right parenthesis after '<number>' in #pragma code_page");
            },
            .code_page_pragma_invalid_code_page => {
                return writer.writeAll("invalid or unknown code page in #pragma code_page");
            },
            .code_page_pragma_not_integer => {
                return writer.writeAll("code page is not a valid integer in #pragma code_page");
            },
            .code_page_pragma_overflow => {
                return writer.writeAll("code page too large in #pragma code_page");
            },
            .code_page_pragma_unsupported_code_page => {
                // We know that the token slice is a well-formed #pragma code_page(N), so
                // we can skip to the first ( and then get the number that follows
                const token_slice = self.token.slice(source);
                var number_start = std.mem.indexOfScalar(u8, token_slice, '(').? + 1;
                while (std.ascii.isWhitespace(token_slice[number_start])) {
                    number_start += 1;
                }
                var number_slice = token_slice[number_start..number_start];
                while (std.ascii.isDigit(token_slice[number_start + number_slice.len])) {
                    number_slice.len += 1;
                }
                const number = std.fmt.parseUnsigned(u16, number_slice, 10) catch unreachable;
                const code_page = code_pages.getByIdentifier(number) catch unreachable;
                // TODO: Improve or maybe add a note making it more clear that the code page
                //       is valid and that the code page is unsupported purely due to a limitation
                //       in this compiler.
                return writer.print("unsupported code page '{s} (id={})' in #pragma code_page", .{ @tagName(code_page), number });
            },
            .unfinished_raw_data_block => {
                return writer.print("unfinished raw data block at '{f}', expected closing '}}' or 'END'", .{self.fmtToken(source)});
            },
            .unfinished_string_table_block => {
                return writer.print("unfinished STRINGTABLE block at '{f}', expected closing '}}' or 'END'", .{self.fmtToken(source)});
            },
            .expected_token => {
                return writer.print("expected '{s}', got '{f}'", .{ self.extra.expected.nameForErrorDisplay(), self.fmtToken(source) });
            },
            .expected_something_else => {
                try writer.writeAll("expected ");
                try self.extra.expected_types.writeCommaSeparated(writer);
                return writer.print("; got '{f}'", .{self.fmtToken(source)});
            },
            .resource_type_cant_use_raw_data => switch (self.type) {
                .err, .warning => try writer.print("expected '<filename>', found '{f}' (resource type '{s}' can't use raw data)", .{ self.fmtToken(source), self.extra.resource.nameForErrorDisplay() }),
                .note => try writer.print("if '{f}' is intended to be a filename, it must be specified as a quoted string literal", .{self.fmtToken(source)}),
                .hint => return,
            },
            .id_must_be_ordinal => {
                try writer.print("id of resource type '{s}' must be an ordinal (u16), got '{f}'", .{ self.extra.resource.nameForErrorDisplay(), self.fmtToken(source) });
            },
            .name_or_id_not_allowed => {
                try writer.print("name or id is not allowed for resource type '{s}'", .{self.extra.resource.nameForErrorDisplay()});
            },
            .string_resource_as_numeric_type => switch (self.type) {
                .err, .warning => try writer.writeAll("the number 6 (RT_STRING) cannot be used as a resource type"),
                .note => try writer.writeAll("using RT_STRING directly likely results in an invalid .res file, use a STRINGTABLE instead"),
                .hint => return,
            },
            .ascii_character_not_equivalent_to_virtual_key_code => {
                // TODO: Better wording? This is what the Win32 RC compiler emits.
                //       This occurs when VIRTKEY and a control code is specified ("^c", etc)
                try writer.writeAll("ASCII character not equivalent to virtual key code");
            },
            .empty_menu_not_allowed => {
                try writer.print("empty menu of type '{f}' not allowed", .{self.fmtToken(source)});
            },
            .rc_would_miscompile_version_value_padding => switch (self.type) {
                .err, .warning => return writer.print("the padding before this quoted string value would be miscompiled by the Win32 RC compiler", .{}),
                .note => return writer.print("to avoid the potential miscompilation, consider adding a comma between the key and the quoted string", .{}),
                .hint => return,
            },
            .rc_would_miscompile_version_value_byte_count => switch (self.type) {
                .err, .warning => return writer.print("the byte count of this value would be miscompiled by the Win32 RC compiler", .{}),
                .note => return writer.print("to avoid the potential miscompilation, do not mix numbers and strings within a value", .{}),
                .hint => return,
            },
            .code_page_pragma_in_included_file => {
                try writer.print("#pragma code_page is not supported in an included resource file", .{});
            },
            .nested_resource_level_exceeds_max => switch (self.type) {
                .err, .warning => {
                    const max = switch (self.extra.resource) {
                        .versioninfo => parse.max_nested_version_level,
                        .menu, .menuex => parse.max_nested_menu_level,
                        else => unreachable,
                    };
                    return writer.print("{s} contains too many nested children (max is {})", .{ self.extra.resource.nameForErrorDisplay(), max });
                },
                .note => return writer.print("max {s} nesting level exceeded here", .{self.extra.resource.nameForErrorDisplay()}),
                .hint => return,
            },
            .too_many_dialog_controls_or_toolbar_buttons => switch (self.type) {
                .err, .warning => return writer.print("{s} contains too many {s} (max is {})", .{ self.extra.resource.nameForErrorDisplay(), switch (self.extra.resource) {
                    .toolbar => "buttons",
                    else => "controls",
                }, std.math.maxInt(u16) }),
                .note => return writer.print("maximum number of {s} exceeded here", .{switch (self.extra.resource) {
                    .toolbar => "buttons",
                    else => "controls",
                }}),
                .hint => return,
            },
            .nested_expression_level_exceeds_max => switch (self.type) {
                .err, .warning => return writer.print("expression contains too many syntax levels (max is {})", .{parse.max_nested_expression_level}),
                .note => return writer.print("maximum expression level exceeded here", .{}),
                .hint => return,
            },
            .close_paren_expression => {
                try writer.writeAll("the Win32 RC compiler would accept ')' as a valid expression, but it would be skipped over and potentially lead to unexpected outcomes");
            },
            .unary_plus_expression => {
                try writer.writeAll("the Win32 RC compiler may accept '+' as a unary operator here, but it is not supported in this implementation; consider omitting the unary +");
            },
            .rc_could_miscompile_control_params => switch (self.type) {
                .err, .warning => return writer.print("this token could be erroneously skipped over by the Win32 RC compiler", .{}),
                .note => return writer.print("to avoid the potential miscompilation, consider adding a comma after the style parameter", .{}),
                .hint => return,
            },
            .dangling_literal_at_eof => {
                try writer.writeAll("dangling literal at end-of-file; this is not a problem, but it is likely a mistake");
            },
            .disjoint_code_page => switch (self.type) {
                .err, .warning => return writer.print("#pragma code_page as the first thing in the .rc script can cause the input and output code pages to become out-of-sync", .{}),
                .note => return writer.print("to avoid unexpected behavior, add a comment (or anything else) above the #pragma code_page line", .{}),
                .hint => return,
            },
            .string_already_defined => switch (self.type) {
                .err, .warning => {
                    const language = self.extra.string_and_language.language;
                    return writer.print("string with id {d} (0x{X}) already defined for language {f}", .{ self.extra.string_and_language.id, self.extra.string_and_language.id, language });
                },
                .note => return writer.print("previous definition of string with id {d} (0x{X}) here", .{ self.extra.string_and_language.id, self.extra.string_and_language.id }),
                .hint => return,
            },
            .font_id_already_defined => switch (self.type) {
                .err => return writer.print("font with id {d} already defined", .{self.extra.number}),
                .warning => return writer.print("skipped duplicate font with id {d}", .{self.extra.number}),
                .note => return writer.print("previous definition of font with id {d} here", .{self.extra.number}),
                .hint => return,
            },
            .file_open_error => {
                try writer.print("unable to open file '{s}': {s}", .{ strings[self.extra.file_open_error.filename_string_index], @tagName(self.extra.file_open_error.err) });
            },
            .invalid_accelerator_key => {
                try writer.print("invalid accelerator key '{f}': {s}", .{ self.fmtToken(source), @tagName(self.extra.accelerator_error.err) });
            },
            .accelerator_type_required => {
                try writer.writeAll("accelerator type [ASCII or VIRTKEY] required when key is an integer");
            },
            .accelerator_shift_or_control_without_virtkey => {
                try writer.writeAll("SHIFT or CONTROL used without VIRTKEY");
            },
            .rc_would_miscompile_control_padding => switch (self.type) {
                .err, .warning => return writer.print("the padding before this control would be miscompiled by the Win32 RC compiler (it would insert 2 extra bytes of padding)", .{}),
                .note => return writer.print("to avoid the potential miscompilation, consider adding one more byte to the control data of the control preceding this one", .{}),
                .hint => return,
            },
            .rc_would_miscompile_control_class_ordinal => switch (self.type) {
                .err, .warning => return writer.print("the control class of this CONTROL would be miscompiled by the Win32 RC compiler", .{}),
                .note => return writer.print("to avoid the potential miscompilation, consider specifying the control class using a string (BUTTON, EDIT, etc) instead of a number", .{}),
                .hint => return,
            },
            .rc_would_error_on_icon_dir => switch (self.type) {
                .err, .warning => return writer.print("the resource at index {} of this {s} has the format '{s}'; this would be an error in the Win32 RC compiler", .{ self.extra.icon_dir.index, @tagName(self.extra.icon_dir.icon_type), @tagName(self.extra.icon_dir.icon_format) }),
                .note => {
                    // The only note supported is one specific to exactly this combination
                    if (!(self.extra.icon_dir.icon_type == .icon and self.extra.icon_dir.icon_format == .riff)) unreachable;
                    try writer.print("animated RIFF icons within resource groups may not be well supported, consider using an animated icon file (.ani) instead", .{});
                },
                .hint => return,
            },
            .format_not_supported_in_icon_dir => {
                try writer.print("resource with format '{s}' (at index {}) is not allowed in {s} resource groups", .{ @tagName(self.extra.icon_dir.icon_format), self.extra.icon_dir.index, @tagName(self.extra.icon_dir.icon_type) });
            },
            .icon_dir_and_resource_type_mismatch => {
                const unexpected_type: rc.ResourceType = if (self.extra.resource == .icon) .cursor else .icon;
                // TODO: Better wording
                try writer.print("resource type '{s}' does not match type '{s}' specified in the file", .{ self.extra.resource.nameForErrorDisplay(), unexpected_type.nameForErrorDisplay() });
            },
            .icon_read_error => {
                try writer.print("unable to read {s} file '{s}': {s}", .{ @tagName(self.extra.icon_read_error.icon_type), strings[self.extra.icon_read_error.filename_string_index], @tagName(self.extra.icon_read_error.err) });
            },
            .rc_would_error_on_bitmap_version => switch (self.type) {
                .err => try writer.print("the DIB at index {} of this {s} is of version '{s}'; this version is no longer allowed and should be upgraded to '{s}'", .{
                    self.extra.icon_dir.index,
                    @tagName(self.extra.icon_dir.icon_type),
                    self.extra.icon_dir.bitmap_version.nameForErrorDisplay(),
                    ico.BitmapHeader.Version.@"nt3.1".nameForErrorDisplay(),
                }),
                .warning => try writer.print("the DIB at index {} of this {s} is of version '{s}'; this would be an error in the Win32 RC compiler", .{
                    self.extra.icon_dir.index,
                    @tagName(self.extra.icon_dir.icon_type),
                    self.extra.icon_dir.bitmap_version.nameForErrorDisplay(),
                }),
                .note => unreachable,
                .hint => return,
            },
            .max_icon_ids_exhausted => switch (self.type) {
                .err, .warning => try writer.print("maximum global icon/cursor ids exhausted (max is {})", .{std.math.maxInt(u16) - 1}),
                .note => try writer.print("maximum icon/cursor id exceeded at index {} of this {s}", .{ self.extra.icon_dir.index, @tagName(self.extra.icon_dir.icon_type) }),
                .hint => return,
            },
            .bmp_read_error => {
                try writer.print("invalid bitmap file '{s}': {s}", .{ strings[self.extra.bmp_read_error.filename_string_index], @tagName(self.extra.bmp_read_error.err) });
            },
            .bmp_ignored_palette_bytes => {
                const bytes = strings[self.extra.number];
                const ignored_bytes = std.mem.readInt(u64, bytes[0..8], native_endian);
                try writer.print("bitmap has {d} extra bytes preceding the pixel data which will be ignored", .{ignored_bytes});
            },
            .bmp_missing_palette_bytes => {
                const bytes = strings[self.extra.number];
                const missing_bytes = std.mem.readInt(u64, bytes[0..8], native_endian);
                try writer.print("bitmap has {d} missing color palette bytes", .{missing_bytes});
            },
            .rc_would_miscompile_bmp_palette_padding => {
                try writer.writeAll("the Win32 RC compiler would erroneously pad out the missing bytes");
                if (self.extra.number != 0) {
                    const bytes = strings[self.extra.number];
                    const miscompiled_bytes = std.mem.readInt(u64, bytes[0..8], native_endian);
                    try writer.print(" (and the added padding bytes would include {d} bytes of the pixel data)", .{miscompiled_bytes});
                }
            },
            .resource_header_size_exceeds_max => {
                try writer.print("resource's header length exceeds maximum of {} bytes", .{std.math.maxInt(u32)});
            },
            .resource_data_size_exceeds_max => switch (self.type) {
                .err, .warning => return writer.print("resource's data length exceeds maximum of {} bytes", .{std.math.maxInt(u32)}),
                .note => return writer.print("maximum data length exceeded here", .{}),
                .hint => return,
            },
            .control_extra_data_size_exceeds_max => switch (self.type) {
                .err, .warning => try writer.print("control data length exceeds maximum of {} bytes", .{std.math.maxInt(u16)}),
                .note => return writer.print("maximum control data length exceeded here", .{}),
                .hint => return,
            },
            .version_node_size_exceeds_max => switch (self.type) {
                .err, .warning => return writer.print("version node tree size exceeds maximum of {} bytes", .{std.math.maxInt(u16)}),
                .note => return writer.print("maximum tree size exceeded while writing this child", .{}),
                .hint => return,
            },
            .fontdir_size_exceeds_max => switch (self.type) {
                .err, .warning => return writer.print("FONTDIR data length exceeds maximum of {} bytes", .{std.math.maxInt(u32)}),
                .note => return writer.writeAll("this is likely due to the size of the combined lengths of the device/face names of all FONT resources"),
                .hint => return,
            },
            .number_expression_as_filename => switch (self.type) {
                .err, .warning => return writer.writeAll("filename cannot be specified using a number expression, consider using a quoted string instead"),
                .note => return writer.print("the Win32 RC compiler would evaluate this number expression as the filename '{s}'", .{strings[self.extra.number]}),
                .hint => return,
            },
            .control_id_already_defined => switch (self.type) {
                .err, .warning => return writer.print("control with id {d} already defined for this dialog", .{self.extra.number}),
                .note => return writer.print("previous definition of control with id {d} here", .{self.extra.number}),
                .hint => return,
            },
            .invalid_filename => {
                const disallowed_codepoint = self.extra.number;
                if (disallowed_codepoint < 128 and std.ascii.isPrint(@intCast(disallowed_codepoint))) {
                    try writer.print("evaluated filename contains a disallowed character: '{c}'", .{@as(u8, @intCast(disallowed_codepoint))});
                } else {
                    try writer.print("evaluated filename contains a disallowed codepoint: <U+{X:0>4}>", .{disallowed_codepoint});
                }
            },
            .rc_would_error_u16_with_l_suffix => switch (self.type) {
                .err, .warning => return writer.print("this {s} parameter would be an error in the Win32 RC compiler", .{@tagName(self.extra.statement_with_u16_param)}),
                .note => return writer.writeAll("to avoid the error, remove any L suffixes from numbers within the parameter"),
                .hint => return,
            },
            .result_contains_fontdir => return,
            .rc_would_miscompile_dialog_menu_id => switch (self.type) {
                .err, .warning => return writer.print("the id of this menu would be miscompiled by the Win32 RC compiler", .{}),
                .note => return writer.print("the Win32 RC compiler would evaluate the id as the ordinal/number value {d}", .{self.extra.number}),
                .hint => return,
            },
            .rc_would_miscompile_dialog_class => switch (self.type) {
                .err, .warning => return writer.print("this class would be miscompiled by the Win32 RC compiler", .{}),
                .note => return writer.print("the Win32 RC compiler would evaluate it as the ordinal/number value {d}", .{self.extra.number}),
                .hint => return,
            },
            .rc_would_miscompile_dialog_menu_or_class_id_forced_ordinal => switch (self.type) {
                .err, .warning => return,
                .note => return writer.print("to avoid the potential miscompilation, only specify one {s} per dialog resource", .{@tagName(self.extra.menu_or_class)}),
                .hint => return,
            },
            .rc_would_miscompile_dialog_menu_id_starts_with_digit => switch (self.type) {
                .err, .warning => return,
                .note => return writer.writeAll("to avoid the potential miscompilation, the first character of the id should not be a digit"),
                .hint => return,
            },
            .dialog_menu_id_was_uppercased => return,
            .duplicate_optional_statement_skipped => {
                return writer.writeAll("this statement was ignored; when multiple statements of the same type are specified, only the last takes precedence");
            },
            .invalid_digit_character_in_ordinal => {
                return writer.writeAll("non-ASCII digit characters are not allowed in ordinal (number) values");
            },
            .rc_would_miscompile_codepoint_whitespace => {
                const treated_as = self.extra.number >> 8;
                return writer.print("codepoint U+{X:0>4} within a string literal would be miscompiled by the Win32 RC compiler (it would get treated as U+{X:0>4})", .{ self.extra.number, treated_as });
            },
            .rc_would_miscompile_codepoint_skip => {
                return writer.print("codepoint U+{X:0>4} within a string literal would be miscompiled by the Win32 RC compiler (the codepoint would be missing from the compiled resource)", .{self.extra.number});
            },
            .rc_would_miscompile_codepoint_bom => switch (self.type) {
                .err, .warning => return writer.print("codepoint U+{X:0>4} within a string literal would cause the entire file to be miscompiled by the Win32 RC compiler", .{self.extra.number}),
                .note => return writer.writeAll("the presence of this codepoint causes all non-ASCII codepoints to be byteswapped by the Win32 RC preprocessor"),
                .hint => return,
            },
            .tab_converted_to_spaces => switch (self.type) {
                .err, .warning => return writer.writeAll("the tab character(s) in this string will be converted into a variable number of spaces (determined by the column of the tab character in the .rc file)"),
                .note => return writer.writeAll("to include the tab character itself in a string, the escape sequence \\t should be used"),
                .hint => return,
            },
            .win32_non_ascii_ordinal => switch (self.type) {
                .err, .warning => unreachable,
                .note => return writer.print("the Win32 RC compiler would accept this as an ordinal but its value would be {}", .{self.extra.number}),
                .hint => return,
            },
            .failed_to_open_cwd => {
                try writer.print("failed to open CWD for compilation: {s}", .{@tagName(self.extra.file_open_error.err)});
            },
        }
    }

    pub const VisualTokenInfo = struct {
        before_len: usize,
        point_offset: usize,
        after_len: usize,
    };

    pub fn visualTokenInfo(self: ErrorDetails, source_line_start: usize, source_line_end: usize, source: []const u8) VisualTokenInfo {
        return switch (self.err) {
            // These can technically be more than 1 byte depending on encoding,
            // but they always refer to one visual character/grapheme.
            .illegal_byte,
            .illegal_byte_outside_string_literals,
            .illegal_codepoint_outside_string_literals,
            .illegal_byte_order_mark,
            .illegal_private_use_character,
            => .{
                .before_len = 0,
                .point_offset = cellCount(self.code_page, source, source_line_start, self.token.start),
                .after_len = 0,
            },
            else => .{
                .before_len = before: {
                    const start = @max(source_line_start, if (self.token_span_start) |span_start| span_start.start else self.token.start);
                    break :before cellCount(self.code_page, source, start, self.token.start);
                },
                .point_offset = cellCount(self.code_page, source, source_line_start, self.token.start),
                .after_len = after: {
                    const end = @min(source_line_end, if (self.token_span_end) |span_end| span_end.end else self.token.end);
                    // end may be less than start when pointing to EOF
                    if (end <= self.token.start) break :after 0;
                    break :after cellCount(self.code_page, source, self.token.start, end) - 1;
                },
            },
        };
    }
}