zig/lib/compiler/aro/aro/Diagnostics.zig
Veikka Tuominen 90ab8ea9e6
Sync Aro sources (#19199)
ref: 02353ad9f17f659e173f68975a442fcec3dd2c94
2024-03-06 14:17:41 -05:00

592 lines
21 KiB
Zig
Vendored

const std = @import("std");
const Allocator = mem.Allocator;
const mem = std.mem;
const Source = @import("Source.zig");
const Compilation = @import("Compilation.zig");
const Attribute = @import("Attribute.zig");
const Builtins = @import("Builtins.zig");
const Builtin = Builtins.Builtin;
const Header = @import("Builtins/Properties.zig").Header;
const Tree = @import("Tree.zig");
const is_windows = @import("builtin").os.tag == .windows;
const LangOpts = @import("LangOpts.zig");
pub const Message = struct {
tag: Tag,
kind: Kind = undefined,
loc: Source.Location = .{},
extra: Extra = .{ .none = {} },
pub const Extra = union {
str: []const u8,
tok_id: struct {
expected: Tree.Token.Id,
actual: Tree.Token.Id,
},
tok_id_expected: Tree.Token.Id,
arguments: struct {
expected: u32,
actual: u32,
},
codepoints: struct {
actual: u21,
resembles: u21,
},
attr_arg_count: struct {
attribute: Attribute.Tag,
expected: u32,
},
attr_arg_type: struct {
expected: Attribute.ArgumentType,
actual: Attribute.ArgumentType,
},
attr_enum: struct {
tag: Attribute.Tag,
},
ignored_record_attr: struct {
tag: Attribute.Tag,
specifier: enum { @"struct", @"union", @"enum" },
},
builtin_with_header: struct {
builtin: Builtin.Tag,
header: Header,
},
invalid_escape: struct {
offset: u32,
char: u8,
},
actual_codepoint: u21,
ascii: u7,
unsigned: u64,
offset: u64,
pow_2_as_string: u8,
signed: i64,
normalized: []const u8,
none: void,
};
};
const Properties = struct {
msg: []const u8,
kind: Kind,
extra: std.meta.FieldEnum(Message.Extra) = .none,
opt: ?u8 = null,
all: bool = false,
w_extra: bool = false,
pedantic: bool = false,
suppress_version: ?LangOpts.Standard = null,
suppress_unless_version: ?LangOpts.Standard = null,
suppress_gnu: bool = false,
suppress_gcc: bool = false,
suppress_clang: bool = false,
suppress_msvc: bool = false,
pub fn makeOpt(comptime str: []const u8) u16 {
return @offsetOf(Options, str);
}
pub fn getKind(prop: Properties, options: *Options) Kind {
const opt = @as([*]Kind, @ptrCast(options))[prop.opt orelse return prop.kind];
if (opt == .default) return prop.kind;
return opt;
}
pub const max_bits = Compilation.bit_int_max_bits;
};
pub const Tag = @import("Diagnostics/messages.zig").with(Properties).Tag;
pub const Kind = enum { @"fatal error", @"error", note, warning, off, default };
pub const Options = struct {
// do not directly use these, instead add `const NAME = true;`
all: Kind = .default,
extra: Kind = .default,
pedantic: Kind = .default,
@"unsupported-pragma": Kind = .default,
@"c99-extensions": Kind = .default,
@"implicit-int": Kind = .default,
@"duplicate-decl-specifier": Kind = .default,
@"missing-declaration": Kind = .default,
@"extern-initializer": Kind = .default,
@"implicit-function-declaration": Kind = .default,
@"unused-value": Kind = .default,
@"unreachable-code": Kind = .default,
@"unknown-warning-option": Kind = .default,
@"gnu-empty-struct": Kind = .default,
@"gnu-alignof-expression": Kind = .default,
@"macro-redefined": Kind = .default,
@"generic-qual-type": Kind = .default,
multichar: Kind = .default,
@"pointer-integer-compare": Kind = .default,
@"compare-distinct-pointer-types": Kind = .default,
@"literal-conversion": Kind = .default,
@"cast-qualifiers": Kind = .default,
@"array-bounds": Kind = .default,
@"int-conversion": Kind = .default,
@"pointer-type-mismatch": Kind = .default,
@"c23-extensions": Kind = .default,
@"incompatible-pointer-types": Kind = .default,
@"excess-initializers": Kind = .default,
@"division-by-zero": Kind = .default,
@"initializer-overrides": Kind = .default,
@"incompatible-pointer-types-discards-qualifiers": Kind = .default,
@"unknown-attributes": Kind = .default,
@"ignored-attributes": Kind = .default,
@"builtin-macro-redefined": Kind = .default,
@"gnu-label-as-value": Kind = .default,
@"malformed-warning-check": Kind = .default,
@"#pragma-messages": Kind = .default,
@"newline-eof": Kind = .default,
@"empty-translation-unit": Kind = .default,
@"implicitly-unsigned-literal": Kind = .default,
@"c99-compat": Kind = .default,
@"unicode-zero-width": Kind = .default,
@"unicode-homoglyph": Kind = .default,
unicode: Kind = .default,
@"return-type": Kind = .default,
@"dollar-in-identifier-extension": Kind = .default,
@"unknown-pragmas": Kind = .default,
@"predefined-identifier-outside-function": Kind = .default,
@"many-braces-around-scalar-init": Kind = .default,
uninitialized: Kind = .default,
@"gnu-statement-expression": Kind = .default,
@"gnu-imaginary-constant": Kind = .default,
@"gnu-complex-integer": Kind = .default,
@"ignored-qualifiers": Kind = .default,
@"integer-overflow": Kind = .default,
@"extra-semi": Kind = .default,
@"gnu-binary-literal": Kind = .default,
@"variadic-macros": Kind = .default,
varargs: Kind = .default,
@"#warnings": Kind = .default,
@"deprecated-declarations": Kind = .default,
@"backslash-newline-escape": Kind = .default,
@"pointer-to-int-cast": Kind = .default,
@"gnu-case-range": Kind = .default,
@"c++-compat": Kind = .default,
vla: Kind = .default,
@"float-overflow-conversion": Kind = .default,
@"float-zero-conversion": Kind = .default,
@"float-conversion": Kind = .default,
@"gnu-folding-constant": Kind = .default,
undef: Kind = .default,
@"ignored-pragmas": Kind = .default,
@"gnu-include-next": Kind = .default,
@"include-next-outside-header": Kind = .default,
@"include-next-absolute-path": Kind = .default,
@"enum-too-large": Kind = .default,
@"fixed-enum-extension": Kind = .default,
@"designated-init": Kind = .default,
@"attribute-warning": Kind = .default,
@"invalid-noreturn": Kind = .default,
@"zero-length-array": Kind = .default,
@"old-style-flexible-struct": Kind = .default,
@"gnu-zero-variadic-macro-arguments": Kind = .default,
@"main-return-type": Kind = .default,
@"expansion-to-defined": Kind = .default,
@"bit-int-extension": Kind = .default,
@"keyword-macro": Kind = .default,
@"pointer-arith": Kind = .default,
@"sizeof-array-argument": Kind = .default,
@"pre-c23-compat": Kind = .default,
@"pointer-bool-conversion": Kind = .default,
@"string-conversion": Kind = .default,
@"gnu-auto-type": Kind = .default,
@"gnu-union-cast": Kind = .default,
@"pointer-sign": Kind = .default,
@"fuse-ld-path": Kind = .default,
@"language-extension-token": Kind = .default,
@"complex-component-init": Kind = .default,
@"microsoft-include": Kind = .default,
@"microsoft-end-of-file": Kind = .default,
@"invalid-source-encoding": Kind = .default,
@"four-char-constants": Kind = .default,
@"unknown-escape-sequence": Kind = .default,
@"invalid-pp-token": Kind = .default,
@"deprecated-non-prototype": Kind = .default,
@"duplicate-embed-param": Kind = .default,
@"unsupported-embed-param": Kind = .default,
@"unused-result": Kind = .default,
normalized: Kind = .default,
@"shift-count-negative": Kind = .default,
@"shift-count-overflow": Kind = .default,
};
const Diagnostics = @This();
list: std.ArrayListUnmanaged(Message) = .{},
arena: std.heap.ArenaAllocator,
fatal_errors: bool = false,
options: Options = .{},
errors: u32 = 0,
macro_backtrace_limit: u32 = 6,
pub fn warningExists(name: []const u8) bool {
inline for (std.meta.fields(Options)) |f| {
if (mem.eql(u8, f.name, name)) return true;
}
return false;
}
pub fn set(d: *Diagnostics, name: []const u8, to: Kind) !void {
inline for (std.meta.fields(Options)) |f| {
if (mem.eql(u8, f.name, name)) {
@field(d.options, f.name) = to;
return;
}
}
try d.addExtra(.{}, .{
.tag = .unknown_warning,
.extra = .{ .str = name },
}, &.{}, true);
}
pub fn init(gpa: Allocator) Diagnostics {
return .{
.arena = std.heap.ArenaAllocator.init(gpa),
};
}
pub fn deinit(d: *Diagnostics) void {
d.list.deinit(d.arena.child_allocator);
d.arena.deinit();
}
pub fn add(comp: *Compilation, msg: Message, expansion_locs: []const Source.Location) Compilation.Error!void {
return comp.diagnostics.addExtra(comp.langopts, msg, expansion_locs, true);
}
pub fn addExtra(
d: *Diagnostics,
langopts: LangOpts,
msg: Message,
expansion_locs: []const Source.Location,
note_msg_loc: bool,
) Compilation.Error!void {
const kind = d.tagKind(msg.tag, langopts);
if (kind == .off) return;
var copy = msg;
copy.kind = kind;
if (expansion_locs.len != 0) copy.loc = expansion_locs[expansion_locs.len - 1];
try d.list.append(d.arena.child_allocator, copy);
if (expansion_locs.len != 0) {
// Add macro backtrace notes in reverse order omitting from the middle if needed.
var i = expansion_locs.len - 1;
const half = d.macro_backtrace_limit / 2;
const limit = if (i < d.macro_backtrace_limit) 0 else i - half;
try d.list.ensureUnusedCapacity(
d.arena.child_allocator,
if (limit == 0) expansion_locs.len else d.macro_backtrace_limit + 1,
);
while (i > limit) {
i -= 1;
d.list.appendAssumeCapacity(.{
.tag = .expanded_from_here,
.kind = .note,
.loc = expansion_locs[i],
});
}
if (limit != 0) {
d.list.appendAssumeCapacity(.{
.tag = .skipping_macro_backtrace,
.kind = .note,
.extra = .{ .unsigned = expansion_locs.len - d.macro_backtrace_limit },
});
i = half -| 1;
while (i > 0) {
i -= 1;
d.list.appendAssumeCapacity(.{
.tag = .expanded_from_here,
.kind = .note,
.loc = expansion_locs[i],
});
}
}
if (note_msg_loc) d.list.appendAssumeCapacity(.{
.tag = .expanded_from_here,
.kind = .note,
.loc = msg.loc,
});
}
if (kind == .@"fatal error" or (kind == .@"error" and d.fatal_errors))
return error.FatalError;
}
pub fn render(comp: *Compilation, config: std.io.tty.Config) void {
if (comp.diagnostics.list.items.len == 0) return;
var m = defaultMsgWriter(config);
defer m.deinit();
renderMessages(comp, &m);
}
pub fn defaultMsgWriter(config: std.io.tty.Config) MsgWriter {
return MsgWriter.init(config);
}
pub fn renderMessages(comp: *Compilation, m: anytype) void {
var errors: u32 = 0;
var warnings: u32 = 0;
for (comp.diagnostics.list.items) |msg| {
switch (msg.kind) {
.@"fatal error", .@"error" => errors += 1,
.warning => warnings += 1,
.note => {},
.off => continue, // happens if an error is added before it is disabled
.default => unreachable,
}
renderMessage(comp, m, msg);
}
const w_s: []const u8 = if (warnings == 1) "" else "s";
const e_s: []const u8 = if (errors == 1) "" else "s";
if (errors != 0 and warnings != 0) {
m.print("{d} warning{s} and {d} error{s} generated.\n", .{ warnings, w_s, errors, e_s });
} else if (warnings != 0) {
m.print("{d} warning{s} generated.\n", .{ warnings, w_s });
} else if (errors != 0) {
m.print("{d} error{s} generated.\n", .{ errors, e_s });
}
comp.diagnostics.list.items.len = 0;
comp.diagnostics.errors += errors;
}
pub fn renderMessage(comp: *Compilation, m: anytype, msg: Message) void {
var line: ?[]const u8 = null;
var end_with_splice = false;
const width = if (msg.loc.id != .unused) blk: {
var loc = msg.loc;
switch (msg.tag) {
.escape_sequence_overflow,
.invalid_universal_character,
=> loc.byte_offset += @truncate(msg.extra.offset),
.non_standard_escape_char,
.unknown_escape_sequence,
=> loc.byte_offset += msg.extra.invalid_escape.offset,
else => {},
}
const source = comp.getSource(loc.id);
var line_col = source.lineCol(loc);
line = line_col.line;
end_with_splice = line_col.end_with_splice;
if (msg.tag == .backslash_newline_escape) {
line = line_col.line[0 .. line_col.col - 1];
line_col.col += 1;
line_col.width += 1;
}
m.location(source.path, line_col.line_no, line_col.col);
break :blk line_col.width;
} else 0;
m.start(msg.kind);
const prop = msg.tag.property();
switch (prop.extra) {
.str => printRt(m, prop.msg, .{"{s}"}, .{msg.extra.str}),
.tok_id => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{
msg.extra.tok_id.expected.symbol(),
msg.extra.tok_id.actual.symbol(),
}),
.tok_id_expected => printRt(m, prop.msg, .{"{s}"}, .{msg.extra.tok_id_expected.symbol()}),
.arguments => printRt(m, prop.msg, .{ "{d}", "{d}" }, .{
msg.extra.arguments.expected,
msg.extra.arguments.actual,
}),
.codepoints => printRt(m, prop.msg, .{ "{X:0>4}", "{u}" }, .{
msg.extra.codepoints.actual,
msg.extra.codepoints.resembles,
}),
.attr_arg_count => printRt(m, prop.msg, .{ "{s}", "{d}" }, .{
@tagName(msg.extra.attr_arg_count.attribute),
msg.extra.attr_arg_count.expected,
}),
.attr_arg_type => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{
msg.extra.attr_arg_type.expected.toString(),
msg.extra.attr_arg_type.actual.toString(),
}),
.actual_codepoint => printRt(m, prop.msg, .{"{X:0>4}"}, .{msg.extra.actual_codepoint}),
.ascii => printRt(m, prop.msg, .{"{c}"}, .{msg.extra.ascii}),
.unsigned => printRt(m, prop.msg, .{"{d}"}, .{msg.extra.unsigned}),
.pow_2_as_string => printRt(m, prop.msg, .{"{s}"}, .{switch (msg.extra.pow_2_as_string) {
63 => "9223372036854775808",
64 => "18446744073709551616",
127 => "170141183460469231731687303715884105728",
128 => "340282366920938463463374607431768211456",
else => unreachable,
}}),
.signed => printRt(m, prop.msg, .{"{d}"}, .{msg.extra.signed}),
.attr_enum => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{
@tagName(msg.extra.attr_enum.tag),
Attribute.Formatting.choices(msg.extra.attr_enum.tag),
}),
.ignored_record_attr => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{
@tagName(msg.extra.ignored_record_attr.tag),
@tagName(msg.extra.ignored_record_attr.specifier),
}),
.builtin_with_header => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{
@tagName(msg.extra.builtin_with_header.header),
Builtin.nameFromTag(msg.extra.builtin_with_header.builtin).span(),
}),
.invalid_escape => {
if (std.ascii.isPrint(msg.extra.invalid_escape.char)) {
const str: [1]u8 = .{msg.extra.invalid_escape.char};
printRt(m, prop.msg, .{"{s}"}, .{&str});
} else {
var buf: [3]u8 = undefined;
const str = std.fmt.bufPrint(&buf, "x{x}", .{std.fmt.fmtSliceHexLower(&.{msg.extra.invalid_escape.char})}) catch unreachable;
printRt(m, prop.msg, .{"{s}"}, .{str});
}
},
.normalized => {
const f = struct {
pub fn f(
bytes: []const u8,
comptime _: []const u8,
_: std.fmt.FormatOptions,
writer: anytype,
) !void {
var it: std.unicode.Utf8Iterator = .{
.bytes = bytes,
.i = 0,
};
while (it.nextCodepoint()) |codepoint| {
if (codepoint < 0x7F) {
try writer.writeByte(@intCast(codepoint));
} else if (codepoint < 0xFFFF) {
try writer.writeAll("\\u");
try std.fmt.formatInt(codepoint, 16, .upper, .{
.fill = '0',
.width = 4,
}, writer);
} else {
try writer.writeAll("\\U");
try std.fmt.formatInt(codepoint, 16, .upper, .{
.fill = '0',
.width = 8,
}, writer);
}
}
}
}.f;
printRt(m, prop.msg, .{"{s}"}, .{
std.fmt.Formatter(f){ .data = msg.extra.normalized },
});
},
.none, .offset => m.write(prop.msg),
}
if (prop.opt) |some| {
if (msg.kind == .@"error" and prop.kind != .@"error") {
m.print(" [-Werror,-W{s}]", .{optName(some)});
} else if (msg.kind != .note) {
m.print(" [-W{s}]", .{optName(some)});
}
}
m.end(line, width, end_with_splice);
}
fn printRt(m: anytype, str: []const u8, comptime fmts: anytype, args: anytype) void {
var i: usize = 0;
inline for (fmts, args) |fmt, arg| {
const new = std.mem.indexOfPos(u8, str, i, fmt).?;
m.write(str[i..new]);
i = new + fmt.len;
m.print(fmt, .{arg});
}
m.write(str[i..]);
}
fn optName(offset: u16) []const u8 {
return std.meta.fieldNames(Options)[offset / @sizeOf(Kind)];
}
fn tagKind(d: *Diagnostics, tag: Tag, langopts: LangOpts) Kind {
const prop = tag.property();
var kind = prop.getKind(&d.options);
if (prop.all) {
if (d.options.all != .default) kind = d.options.all;
}
if (prop.w_extra) {
if (d.options.extra != .default) kind = d.options.extra;
}
if (prop.pedantic) {
if (d.options.pedantic != .default) kind = d.options.pedantic;
}
if (prop.suppress_version) |some| if (langopts.standard.atLeast(some)) return .off;
if (prop.suppress_unless_version) |some| if (!langopts.standard.atLeast(some)) return .off;
if (prop.suppress_gnu and langopts.standard.isExplicitGNU()) return .off;
if (prop.suppress_gcc and langopts.emulate == .gcc) return .off;
if (prop.suppress_clang and langopts.emulate == .clang) return .off;
if (prop.suppress_msvc and langopts.emulate == .msvc) return .off;
if (kind == .@"error" and d.fatal_errors) kind = .@"fatal error";
return kind;
}
const MsgWriter = struct {
w: std.io.BufferedWriter(4096, std.fs.File.Writer),
config: std.io.tty.Config,
fn init(config: std.io.tty.Config) MsgWriter {
std.debug.getStderrMutex().lock();
return .{
.w = std.io.bufferedWriter(std.io.getStdErr().writer()),
.config = config,
};
}
pub fn deinit(m: *MsgWriter) void {
m.w.flush() catch {};
std.debug.getStderrMutex().unlock();
}
pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void {
m.w.writer().print(fmt, args) catch {};
}
fn write(m: *MsgWriter, msg: []const u8) void {
m.w.writer().writeAll(msg) catch {};
}
fn setColor(m: *MsgWriter, color: std.io.tty.Color) void {
m.config.setColor(m.w.writer(), color) catch {};
}
fn location(m: *MsgWriter, path: []const u8, line: u32, col: u32) void {
m.setColor(.bold);
m.print("{s}:{d}:{d}: ", .{ path, line, col });
}
fn start(m: *MsgWriter, kind: Kind) void {
switch (kind) {
.@"fatal error", .@"error" => m.setColor(.bright_red),
.note => m.setColor(.bright_cyan),
.warning => m.setColor(.bright_magenta),
.off, .default => unreachable,
}
m.write(switch (kind) {
.@"fatal error" => "fatal error: ",
.@"error" => "error: ",
.note => "note: ",
.warning => "warning: ",
.off, .default => unreachable,
});
m.setColor(.white);
}
fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void {
const line = maybe_line orelse {
m.write("\n");
m.setColor(.reset);
return;
};
const trailer = if (end_with_splice) "\\ " else "";
m.setColor(.reset);
m.print("\n{s}{s}\n{s: >[3]}", .{ line, trailer, "", col });
m.setColor(.bold);
m.setColor(.bright_green);
m.write("^\n");
m.setColor(.reset);
}
};