mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
516 lines
19 KiB
Zig
516 lines
19 KiB
Zig
//! To support incremental compilation, errors are stored in various places
|
|
//! so that they can be created and destroyed appropriately. This structure
|
|
//! is used to collect all the errors from the various places into one
|
|
//! convenient place for API users to consume.
|
|
//!
|
|
//! There is one special encoding for this data structure. If both arrays are
|
|
//! empty, it means there are no errors. This special encoding exists so that
|
|
//! heap allocation is not needed in the common case of no errors.
|
|
|
|
string_bytes: []const u8,
|
|
/// The first thing in this array is an `ErrorMessageList`.
|
|
extra: []const u32,
|
|
|
|
/// Special encoding when there are no errors.
|
|
pub const empty: ErrorBundle = .{
|
|
.string_bytes = &.{},
|
|
.extra = &.{},
|
|
};
|
|
|
|
// An index into `extra` pointing at an `ErrorMessage`.
|
|
pub const MessageIndex = enum(u32) {
|
|
_,
|
|
};
|
|
|
|
// An index into `extra` pointing at an `SourceLocation`.
|
|
pub const SourceLocationIndex = enum(u32) {
|
|
none = 0,
|
|
_,
|
|
};
|
|
|
|
/// There will be a MessageIndex for each len at start.
|
|
pub const ErrorMessageList = struct {
|
|
len: u32,
|
|
start: u32,
|
|
/// null-terminated string index. 0 means no compile log text.
|
|
compile_log_text: u32,
|
|
};
|
|
|
|
/// Trailing:
|
|
/// * ReferenceTrace for each reference_trace_len
|
|
pub const SourceLocation = struct {
|
|
/// null terminated string index
|
|
src_path: u32,
|
|
line: u32,
|
|
column: u32,
|
|
/// byte offset of starting token
|
|
span_start: u32,
|
|
/// byte offset of main error location
|
|
span_main: u32,
|
|
/// byte offset of end of last token
|
|
span_end: u32,
|
|
/// null terminated string index, possibly null.
|
|
/// Does not include the trailing newline.
|
|
source_line: u32 = 0,
|
|
reference_trace_len: u32 = 0,
|
|
};
|
|
|
|
/// Trailing:
|
|
/// * MessageIndex for each notes_len.
|
|
pub const ErrorMessage = struct {
|
|
/// null terminated string index
|
|
msg: u32,
|
|
/// Usually one, but incremented for redundant messages.
|
|
count: u32 = 1,
|
|
src_loc: SourceLocationIndex = .none,
|
|
notes_len: u32 = 0,
|
|
};
|
|
|
|
pub const ReferenceTrace = struct {
|
|
/// null terminated string index
|
|
/// Except for the sentinel ReferenceTrace element, in which case:
|
|
/// * 0 means remaining references hidden
|
|
/// * >0 means N references hidden
|
|
decl_name: u32,
|
|
/// Index into extra of a SourceLocation
|
|
/// If this is 0, this is the sentinel ReferenceTrace element.
|
|
src_loc: SourceLocationIndex,
|
|
};
|
|
|
|
pub fn deinit(eb: *ErrorBundle, gpa: Allocator) void {
|
|
gpa.free(eb.string_bytes);
|
|
gpa.free(eb.extra);
|
|
eb.* = undefined;
|
|
}
|
|
|
|
pub fn errorMessageCount(eb: ErrorBundle) u32 {
|
|
if (eb.extra.len == 0) return 0;
|
|
return eb.getErrorMessageList().len;
|
|
}
|
|
|
|
pub fn getErrorMessageList(eb: ErrorBundle) ErrorMessageList {
|
|
return eb.extraData(ErrorMessageList, 0).data;
|
|
}
|
|
|
|
pub fn getMessages(eb: ErrorBundle) []const MessageIndex {
|
|
const list = eb.getErrorMessageList();
|
|
return @ptrCast([]const MessageIndex, eb.extra[list.start..][0..list.len]);
|
|
}
|
|
|
|
pub fn getErrorMessage(eb: ErrorBundle, index: MessageIndex) ErrorMessage {
|
|
return eb.extraData(ErrorMessage, @enumToInt(index)).data;
|
|
}
|
|
|
|
pub fn getSourceLocation(eb: ErrorBundle, index: SourceLocationIndex) SourceLocation {
|
|
assert(index != .none);
|
|
return eb.extraData(SourceLocation, @enumToInt(index)).data;
|
|
}
|
|
|
|
pub fn getNotes(eb: ErrorBundle, index: MessageIndex) []const MessageIndex {
|
|
const notes_len = eb.getErrorMessage(index).notes_len;
|
|
const start = @enumToInt(index) + @typeInfo(ErrorMessage).Struct.fields.len;
|
|
return @ptrCast([]const MessageIndex, eb.extra[start..][0..notes_len]);
|
|
}
|
|
|
|
pub fn getCompileLogOutput(eb: ErrorBundle) [:0]const u8 {
|
|
return nullTerminatedString(eb, getErrorMessageList(eb).compile_log_text);
|
|
}
|
|
|
|
/// Returns the requested data, as well as the new index which is at the start of the
|
|
/// trailers for the object.
|
|
fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T, end: usize } {
|
|
const fields = @typeInfo(T).Struct.fields;
|
|
var i: usize = index;
|
|
var result: T = undefined;
|
|
inline for (fields) |field| {
|
|
@field(result, field.name) = switch (field.type) {
|
|
u32 => eb.extra[i],
|
|
MessageIndex => @intToEnum(MessageIndex, eb.extra[i]),
|
|
SourceLocationIndex => @intToEnum(SourceLocationIndex, eb.extra[i]),
|
|
else => @compileError("bad field type"),
|
|
};
|
|
i += 1;
|
|
}
|
|
return .{
|
|
.data = result,
|
|
.end = i,
|
|
};
|
|
}
|
|
|
|
/// Given an index into `string_bytes` returns the null-terminated string found there.
|
|
pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 {
|
|
const string_bytes = eb.string_bytes;
|
|
var end: usize = index;
|
|
while (string_bytes[end] != 0) {
|
|
end += 1;
|
|
}
|
|
return string_bytes[index..end :0];
|
|
}
|
|
|
|
pub const RenderOptions = struct {
|
|
ttyconf: std.debug.TTY.Config,
|
|
include_reference_trace: bool = true,
|
|
include_source_line: bool = true,
|
|
include_log_text: bool = true,
|
|
};
|
|
|
|
pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void {
|
|
std.debug.getStderrMutex().lock();
|
|
defer std.debug.getStderrMutex().unlock();
|
|
const stderr = std.io.getStdErr();
|
|
return renderToWriter(eb, options, stderr.writer()) catch return;
|
|
}
|
|
|
|
pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) anyerror!void {
|
|
for (eb.getMessages()) |err_msg| {
|
|
try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .Red, 0);
|
|
}
|
|
|
|
if (options.include_log_text) {
|
|
const log_text = eb.getCompileLogOutput();
|
|
if (log_text.len != 0) {
|
|
try writer.writeAll("\nCompile Log Output:\n");
|
|
try writer.writeAll(log_text);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn renderErrorMessageToWriter(
|
|
eb: ErrorBundle,
|
|
options: RenderOptions,
|
|
err_msg_index: MessageIndex,
|
|
stderr: anytype,
|
|
kind: []const u8,
|
|
color: std.debug.TTY.Color,
|
|
indent: usize,
|
|
) anyerror!void {
|
|
const ttyconf = options.ttyconf;
|
|
var counting_writer = std.io.countingWriter(stderr);
|
|
const counting_stderr = counting_writer.writer();
|
|
const err_msg = eb.getErrorMessage(err_msg_index);
|
|
if (err_msg.src_loc != .none) {
|
|
const src = eb.extraData(SourceLocation, @enumToInt(err_msg.src_loc));
|
|
try counting_stderr.writeByteNTimes(' ', indent);
|
|
try ttyconf.setColor(stderr, .Bold);
|
|
try counting_stderr.print("{s}:{d}:{d}: ", .{
|
|
eb.nullTerminatedString(src.data.src_path),
|
|
src.data.line + 1,
|
|
src.data.column + 1,
|
|
});
|
|
try ttyconf.setColor(stderr, color);
|
|
try counting_stderr.writeAll(kind);
|
|
try counting_stderr.writeAll(": ");
|
|
// This is the length of the part before the error message:
|
|
// e.g. "file.zig:4:5: error: "
|
|
const prefix_len = @intCast(usize, counting_stderr.context.bytes_written);
|
|
try ttyconf.setColor(stderr, .Reset);
|
|
try ttyconf.setColor(stderr, .Bold);
|
|
if (err_msg.count == 1) {
|
|
try writeMsg(eb, err_msg, stderr, prefix_len);
|
|
try stderr.writeByte('\n');
|
|
} else {
|
|
try writeMsg(eb, err_msg, stderr, prefix_len);
|
|
try ttyconf.setColor(stderr, .Dim);
|
|
try stderr.print(" ({d} times)\n", .{err_msg.count});
|
|
}
|
|
try ttyconf.setColor(stderr, .Reset);
|
|
if (src.data.source_line != 0 and options.include_source_line) {
|
|
const line = eb.nullTerminatedString(src.data.source_line);
|
|
for (line) |b| switch (b) {
|
|
'\t' => try stderr.writeByte(' '),
|
|
else => try stderr.writeByte(b),
|
|
};
|
|
try stderr.writeByte('\n');
|
|
// TODO basic unicode code point monospace width
|
|
const before_caret = src.data.span_main - src.data.span_start;
|
|
// -1 since span.main includes the caret
|
|
const after_caret = src.data.span_end - src.data.span_main -| 1;
|
|
try stderr.writeByteNTimes(' ', src.data.column - before_caret);
|
|
try ttyconf.setColor(stderr, .Green);
|
|
try stderr.writeByteNTimes('~', before_caret);
|
|
try stderr.writeByte('^');
|
|
try stderr.writeByteNTimes('~', after_caret);
|
|
try stderr.writeByte('\n');
|
|
try ttyconf.setColor(stderr, .Reset);
|
|
}
|
|
for (eb.getNotes(err_msg_index)) |note| {
|
|
try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent);
|
|
}
|
|
if (src.data.reference_trace_len > 0 and options.include_reference_trace) {
|
|
try ttyconf.setColor(stderr, .Reset);
|
|
try ttyconf.setColor(stderr, .Dim);
|
|
try stderr.print("referenced by:\n", .{});
|
|
var ref_index = src.end;
|
|
for (0..src.data.reference_trace_len) |_| {
|
|
const ref_trace = eb.extraData(ReferenceTrace, ref_index);
|
|
ref_index = ref_trace.end;
|
|
if (ref_trace.data.src_loc != .none) {
|
|
const ref_src = eb.getSourceLocation(ref_trace.data.src_loc);
|
|
try stderr.print(" {s}: {s}:{d}:{d}\n", .{
|
|
eb.nullTerminatedString(ref_trace.data.decl_name),
|
|
eb.nullTerminatedString(ref_src.src_path),
|
|
ref_src.line + 1,
|
|
ref_src.column + 1,
|
|
});
|
|
} else if (ref_trace.data.decl_name != 0) {
|
|
const count = ref_trace.data.decl_name;
|
|
try stderr.print(
|
|
" {d} reference(s) hidden; use '-freference-trace={d}' to see all references\n",
|
|
.{ count, count + src.data.reference_trace_len - 1 },
|
|
);
|
|
} else {
|
|
try stderr.print(
|
|
" remaining reference traces hidden; use '-freference-trace' to see all reference traces\n",
|
|
.{},
|
|
);
|
|
}
|
|
}
|
|
try stderr.writeByte('\n');
|
|
try ttyconf.setColor(stderr, .Reset);
|
|
}
|
|
} else {
|
|
try ttyconf.setColor(stderr, color);
|
|
try stderr.writeByteNTimes(' ', indent);
|
|
try stderr.writeAll(kind);
|
|
try stderr.writeAll(": ");
|
|
try ttyconf.setColor(stderr, .Reset);
|
|
const msg = eb.nullTerminatedString(err_msg.msg);
|
|
if (err_msg.count == 1) {
|
|
try stderr.print("{s}\n", .{msg});
|
|
} else {
|
|
try stderr.print("{s}", .{msg});
|
|
try ttyconf.setColor(stderr, .Dim);
|
|
try stderr.print(" ({d} times)\n", .{err_msg.count});
|
|
}
|
|
try ttyconf.setColor(stderr, .Reset);
|
|
for (eb.getNotes(err_msg_index)) |note| {
|
|
try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent + 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Splits the error message up into lines to properly indent them
|
|
/// to allow for long, good-looking error messages.
|
|
///
|
|
/// This is used to split the message in `@compileError("hello\nworld")` for example.
|
|
fn writeMsg(eb: ErrorBundle, err_msg: ErrorMessage, stderr: anytype, indent: usize) !void {
|
|
var lines = std.mem.split(u8, eb.nullTerminatedString(err_msg.msg), "\n");
|
|
while (lines.next()) |line| {
|
|
try stderr.writeAll(line);
|
|
if (lines.index == null) break;
|
|
try stderr.writeByte('\n');
|
|
try stderr.writeByteNTimes(' ', indent);
|
|
}
|
|
}
|
|
|
|
const std = @import("std");
|
|
const ErrorBundle = @This();
|
|
const Allocator = std.mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
|
|
pub const Wip = struct {
|
|
gpa: Allocator,
|
|
string_bytes: std.ArrayListUnmanaged(u8),
|
|
/// The first thing in this array is a ErrorMessageList.
|
|
extra: std.ArrayListUnmanaged(u32),
|
|
root_list: std.ArrayListUnmanaged(MessageIndex),
|
|
|
|
pub fn init(wip: *Wip, gpa: Allocator) !void {
|
|
wip.* = .{
|
|
.gpa = gpa,
|
|
.string_bytes = .{},
|
|
.extra = .{},
|
|
.root_list = .{},
|
|
};
|
|
|
|
// So that 0 can be used to indicate a null string.
|
|
try wip.string_bytes.append(gpa, 0);
|
|
|
|
assert(0 == try addExtra(wip, ErrorMessageList{
|
|
.len = 0,
|
|
.start = 0,
|
|
.compile_log_text = 0,
|
|
}));
|
|
}
|
|
|
|
pub fn deinit(wip: *Wip) void {
|
|
const gpa = wip.gpa;
|
|
wip.root_list.deinit(gpa);
|
|
wip.string_bytes.deinit(gpa);
|
|
wip.extra.deinit(gpa);
|
|
wip.* = undefined;
|
|
}
|
|
|
|
pub fn toOwnedBundle(wip: *Wip, compile_log_text: []const u8) !ErrorBundle {
|
|
const gpa = wip.gpa;
|
|
if (wip.root_list.items.len == 0) {
|
|
assert(compile_log_text.len == 0);
|
|
// Special encoding when there are no errors.
|
|
wip.deinit();
|
|
wip.* = .{
|
|
.gpa = gpa,
|
|
.string_bytes = .{},
|
|
.extra = .{},
|
|
.root_list = .{},
|
|
};
|
|
return empty;
|
|
}
|
|
|
|
const compile_log_str_index = if (compile_log_text.len == 0) 0 else str: {
|
|
const str = @intCast(u32, wip.string_bytes.items.len);
|
|
try wip.string_bytes.ensureUnusedCapacity(gpa, compile_log_text.len + 1);
|
|
wip.string_bytes.appendSliceAssumeCapacity(compile_log_text);
|
|
wip.string_bytes.appendAssumeCapacity(0);
|
|
break :str str;
|
|
};
|
|
|
|
wip.setExtra(0, ErrorMessageList{
|
|
.len = @intCast(u32, wip.root_list.items.len),
|
|
.start = @intCast(u32, wip.extra.items.len),
|
|
.compile_log_text = compile_log_str_index,
|
|
});
|
|
try wip.extra.appendSlice(gpa, @ptrCast([]const u32, wip.root_list.items));
|
|
wip.root_list.clearAndFree(gpa);
|
|
return .{
|
|
.string_bytes = try wip.string_bytes.toOwnedSlice(gpa),
|
|
.extra = try wip.extra.toOwnedSlice(gpa),
|
|
};
|
|
}
|
|
|
|
pub fn tmpBundle(wip: Wip) ErrorBundle {
|
|
return .{
|
|
.string_bytes = wip.string_bytes.items,
|
|
.extra = wip.extra.items,
|
|
};
|
|
}
|
|
|
|
pub fn addString(wip: *Wip, s: []const u8) !u32 {
|
|
const gpa = wip.gpa;
|
|
const index = @intCast(u32, wip.string_bytes.items.len);
|
|
try wip.string_bytes.ensureUnusedCapacity(gpa, s.len + 1);
|
|
wip.string_bytes.appendSliceAssumeCapacity(s);
|
|
wip.string_bytes.appendAssumeCapacity(0);
|
|
return index;
|
|
}
|
|
|
|
pub fn printString(wip: *Wip, comptime fmt: []const u8, args: anytype) !u32 {
|
|
const gpa = wip.gpa;
|
|
const index = @intCast(u32, wip.string_bytes.items.len);
|
|
try wip.string_bytes.writer(gpa).print(fmt, args);
|
|
try wip.string_bytes.append(gpa, 0);
|
|
return index;
|
|
}
|
|
|
|
pub fn addRootErrorMessage(wip: *Wip, em: ErrorMessage) !void {
|
|
try wip.root_list.ensureUnusedCapacity(wip.gpa, 1);
|
|
wip.root_list.appendAssumeCapacity(try addErrorMessage(wip, em));
|
|
}
|
|
|
|
pub fn addErrorMessage(wip: *Wip, em: ErrorMessage) !MessageIndex {
|
|
return @intToEnum(MessageIndex, try addExtra(wip, em));
|
|
}
|
|
|
|
pub fn addErrorMessageAssumeCapacity(wip: *Wip, em: ErrorMessage) MessageIndex {
|
|
return @intToEnum(MessageIndex, addExtraAssumeCapacity(wip, em));
|
|
}
|
|
|
|
pub fn addSourceLocation(wip: *Wip, sl: SourceLocation) !SourceLocationIndex {
|
|
return @intToEnum(SourceLocationIndex, try addExtra(wip, sl));
|
|
}
|
|
|
|
pub fn addReferenceTrace(wip: *Wip, rt: ReferenceTrace) !void {
|
|
_ = try addExtra(wip, rt);
|
|
}
|
|
|
|
pub fn addBundle(wip: *Wip, other: ErrorBundle) !void {
|
|
const gpa = wip.gpa;
|
|
|
|
try wip.string_bytes.ensureUnusedCapacity(gpa, other.string_bytes.len);
|
|
try wip.extra.ensureUnusedCapacity(gpa, other.extra.len);
|
|
|
|
const other_list = other.getMessages();
|
|
|
|
// The ensureUnusedCapacity call above guarantees this.
|
|
const notes_start = wip.reserveNotes(@intCast(u32, other_list.len)) catch unreachable;
|
|
for (notes_start.., other_list) |note, message| {
|
|
wip.extra.items[note] = @enumToInt(wip.addOtherMessage(other, message) catch unreachable);
|
|
}
|
|
}
|
|
|
|
pub fn reserveNotes(wip: *Wip, notes_len: u32) !u32 {
|
|
try wip.extra.ensureUnusedCapacity(wip.gpa, notes_len +
|
|
notes_len * @typeInfo(ErrorBundle.ErrorMessage).Struct.fields.len);
|
|
wip.extra.items.len += notes_len;
|
|
return @intCast(u32, wip.extra.items.len - notes_len);
|
|
}
|
|
|
|
fn addOtherMessage(wip: *Wip, other: ErrorBundle, msg_index: MessageIndex) !MessageIndex {
|
|
const other_msg = other.getErrorMessage(msg_index);
|
|
const src_loc = try wip.addOtherSourceLocation(other, other_msg.src_loc);
|
|
const msg = try wip.addErrorMessage(.{
|
|
.msg = try wip.addString(other.nullTerminatedString(other_msg.msg)),
|
|
.count = other_msg.count,
|
|
.src_loc = src_loc,
|
|
.notes_len = other_msg.notes_len,
|
|
});
|
|
const notes_start = try wip.reserveNotes(other_msg.notes_len);
|
|
for (notes_start.., other.getNotes(msg_index)) |note, other_note| {
|
|
wip.extra.items[note] = @enumToInt(try wip.addOtherMessage(other, other_note));
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
fn addOtherSourceLocation(
|
|
wip: *Wip,
|
|
other: ErrorBundle,
|
|
index: SourceLocationIndex,
|
|
) !SourceLocationIndex {
|
|
if (index == .none) return .none;
|
|
const other_sl = other.getSourceLocation(index);
|
|
|
|
const src_loc = try wip.addSourceLocation(.{
|
|
.src_path = try wip.addString(other.nullTerminatedString(other_sl.src_path)),
|
|
.line = other_sl.line,
|
|
.column = other_sl.column,
|
|
.span_start = other_sl.span_start,
|
|
.span_main = other_sl.span_main,
|
|
.span_end = other_sl.span_end,
|
|
.source_line = try wip.addString(other.nullTerminatedString(other_sl.source_line)),
|
|
.reference_trace_len = other_sl.reference_trace_len,
|
|
});
|
|
|
|
// TODO: also add the reference trace
|
|
|
|
return src_loc;
|
|
}
|
|
|
|
fn addExtra(wip: *Wip, extra: anytype) Allocator.Error!u32 {
|
|
const gpa = wip.gpa;
|
|
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
|
|
try wip.extra.ensureUnusedCapacity(gpa, fields.len);
|
|
return addExtraAssumeCapacity(wip, extra);
|
|
}
|
|
|
|
fn addExtraAssumeCapacity(wip: *Wip, extra: anytype) u32 {
|
|
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
|
|
const result = @intCast(u32, wip.extra.items.len);
|
|
wip.extra.items.len += fields.len;
|
|
setExtra(wip, result, extra);
|
|
return result;
|
|
}
|
|
|
|
fn setExtra(wip: *Wip, index: usize, extra: anytype) void {
|
|
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
|
|
var i = index;
|
|
inline for (fields) |field| {
|
|
wip.extra.items[i] = switch (field.type) {
|
|
u32 => @field(extra, field.name),
|
|
MessageIndex => @enumToInt(@field(extra, field.name)),
|
|
SourceLocationIndex => @enumToInt(@field(extra, field.name)),
|
|
else => @compileError("bad field type"),
|
|
};
|
|
i += 1;
|
|
}
|
|
}
|
|
};
|