mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
834 lines
32 KiB
Zig
834 lines
32 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.
|
|
const ErrorBundle = @This();
|
|
|
|
const std = @import("std");
|
|
const Io = std.Io;
|
|
const Writer = std.Io.Writer;
|
|
const Allocator = std.mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
|
|
string_bytes: []const u8,
|
|
/// The first thing in this array is an `ErrorMessageList`.
|
|
extra: []const u32,
|
|
|
|
/// Index into `string_bytes`.
|
|
pub const String = u32;
|
|
/// Index into `string_bytes`, or null.
|
|
pub const OptionalString = 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: OptionalString,
|
|
};
|
|
|
|
/// Trailing:
|
|
/// * ReferenceTrace for each reference_trace_len
|
|
pub const SourceLocation = struct {
|
|
src_path: String,
|
|
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,
|
|
/// Does not include the trailing newline.
|
|
source_line: OptionalString = 0,
|
|
reference_trace_len: u32 = 0,
|
|
};
|
|
|
|
/// Trailing:
|
|
/// * MessageIndex for each notes_len.
|
|
pub const ErrorMessage = struct {
|
|
msg: String,
|
|
/// 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: String,
|
|
/// 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 @as([]const MessageIndex, @ptrCast(eb.extra[list.start..][0..list.len]));
|
|
}
|
|
|
|
pub fn getErrorMessage(eb: ErrorBundle, index: MessageIndex) ErrorMessage {
|
|
return eb.extraData(ErrorMessage, @intFromEnum(index)).data;
|
|
}
|
|
|
|
pub fn getSourceLocation(eb: ErrorBundle, index: SourceLocationIndex) SourceLocation {
|
|
assert(index != .none);
|
|
return eb.extraData(SourceLocation, @intFromEnum(index)).data;
|
|
}
|
|
|
|
pub fn getNotes(eb: ErrorBundle, index: MessageIndex) []const MessageIndex {
|
|
const notes_len = eb.getErrorMessage(index).notes_len;
|
|
const start = @intFromEnum(index) + @typeInfo(ErrorMessage).@"struct".fields.len;
|
|
return @as([]const MessageIndex, @ptrCast(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 => @as(MessageIndex, @enumFromInt(eb.extra[i])),
|
|
SourceLocationIndex => @as(SourceLocationIndex, @enumFromInt(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: String) [: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 {
|
|
include_reference_trace: bool = true,
|
|
include_source_line: bool = true,
|
|
include_log_text: bool = true,
|
|
};
|
|
|
|
pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions, color: std.zig.Color) void {
|
|
var buffer: [256]u8 = undefined;
|
|
const w, const ttyconf = std.debug.lockStderrWriter(&buffer);
|
|
defer std.debug.unlockStderrWriter();
|
|
renderToWriter(eb, options, w, color.getTtyConf(ttyconf)) catch return;
|
|
}
|
|
|
|
pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, w: *Writer, ttyconf: Io.tty.Config) (Writer.Error || std.posix.UnexpectedError)!void {
|
|
if (eb.extra.len == 0) return;
|
|
for (eb.getMessages()) |err_msg| {
|
|
try renderErrorMessageToWriter(eb, options, err_msg, w, ttyconf, "error", .red, 0);
|
|
}
|
|
|
|
if (options.include_log_text) {
|
|
const log_text = eb.getCompileLogOutput();
|
|
if (log_text.len != 0) {
|
|
try w.writeAll("\nCompile Log Output:\n");
|
|
try w.writeAll(log_text);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn renderErrorMessageToWriter(
|
|
eb: ErrorBundle,
|
|
options: RenderOptions,
|
|
err_msg_index: MessageIndex,
|
|
w: *Writer,
|
|
ttyconf: Io.tty.Config,
|
|
kind: []const u8,
|
|
color: Io.tty.Color,
|
|
indent: usize,
|
|
) (Writer.Error || std.posix.UnexpectedError)!void {
|
|
const err_msg = eb.getErrorMessage(err_msg_index);
|
|
if (err_msg.src_loc != .none) {
|
|
const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc));
|
|
var prefix: Writer.Discarding = .init(&.{});
|
|
try w.splatByteAll(' ', indent);
|
|
prefix.count += indent;
|
|
try ttyconf.setColor(w, .bold);
|
|
try w.print("{s}:{d}:{d}: ", .{
|
|
eb.nullTerminatedString(src.data.src_path),
|
|
src.data.line + 1,
|
|
src.data.column + 1,
|
|
});
|
|
try prefix.writer.print("{s}:{d}:{d}: ", .{
|
|
eb.nullTerminatedString(src.data.src_path),
|
|
src.data.line + 1,
|
|
src.data.column + 1,
|
|
});
|
|
try ttyconf.setColor(w, color);
|
|
try w.writeAll(kind);
|
|
prefix.count += kind.len;
|
|
try w.writeAll(": ");
|
|
prefix.count += 2;
|
|
// This is the length of the part before the error message:
|
|
// e.g. "file.zig:4:5: error: "
|
|
const prefix_len: usize = @intCast(prefix.count);
|
|
try ttyconf.setColor(w, .reset);
|
|
try ttyconf.setColor(w, .bold);
|
|
if (err_msg.count == 1) {
|
|
try writeMsg(eb, err_msg, w, prefix_len);
|
|
try w.writeByte('\n');
|
|
} else {
|
|
try writeMsg(eb, err_msg, w, prefix_len);
|
|
try ttyconf.setColor(w, .dim);
|
|
try w.print(" ({d} times)\n", .{err_msg.count});
|
|
}
|
|
try ttyconf.setColor(w, .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 w.writeByte(' '),
|
|
else => try w.writeByte(b),
|
|
};
|
|
try w.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 w.splatByteAll(' ', src.data.column - before_caret);
|
|
try ttyconf.setColor(w, .green);
|
|
try w.splatByteAll('~', before_caret);
|
|
try w.writeByte('^');
|
|
try w.splatByteAll('~', after_caret);
|
|
try w.writeByte('\n');
|
|
try ttyconf.setColor(w, .reset);
|
|
}
|
|
for (eb.getNotes(err_msg_index)) |note| {
|
|
try renderErrorMessageToWriter(eb, options, note, w, ttyconf, "note", .cyan, indent);
|
|
}
|
|
if (src.data.reference_trace_len > 0 and options.include_reference_trace) {
|
|
try ttyconf.setColor(w, .reset);
|
|
try ttyconf.setColor(w, .dim);
|
|
try w.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 w.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 w.print(
|
|
" {d} reference(s) hidden; use '-freference-trace={d}' to see all references\n",
|
|
.{ count, count + src.data.reference_trace_len - 1 },
|
|
);
|
|
} else {
|
|
try w.print(
|
|
" remaining reference traces hidden; use '-freference-trace' to see all reference traces\n",
|
|
.{},
|
|
);
|
|
}
|
|
}
|
|
try ttyconf.setColor(w, .reset);
|
|
}
|
|
} else {
|
|
try ttyconf.setColor(w, color);
|
|
try w.splatByteAll(' ', indent);
|
|
try w.writeAll(kind);
|
|
try w.writeAll(": ");
|
|
try ttyconf.setColor(w, .reset);
|
|
const msg = eb.nullTerminatedString(err_msg.msg);
|
|
if (err_msg.count == 1) {
|
|
try w.print("{s}\n", .{msg});
|
|
} else {
|
|
try w.print("{s}", .{msg});
|
|
try ttyconf.setColor(w, .dim);
|
|
try w.print(" ({d} times)\n", .{err_msg.count});
|
|
}
|
|
try ttyconf.setColor(w, .reset);
|
|
for (eb.getNotes(err_msg_index)) |note| {
|
|
try renderErrorMessageToWriter(eb, options, note, w, ttyconf, "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, w: *Writer, indent: usize) !void {
|
|
var lines = std.mem.splitScalar(u8, eb.nullTerminatedString(err_msg.msg), '\n');
|
|
while (lines.next()) |line| {
|
|
try w.writeAll(line);
|
|
if (lines.index == null) break;
|
|
try w.writeByte('\n');
|
|
try w.splatByteAll(' ', indent);
|
|
}
|
|
}
|
|
|
|
pub const Wip = struct {
|
|
gpa: Allocator,
|
|
string_bytes: std.ArrayList(u8),
|
|
/// The first thing in this array is a ErrorMessageList.
|
|
extra: std.ArrayList(u32),
|
|
root_list: std.ArrayList(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: u32 = @intCast(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(wip.root_list.items.len),
|
|
.start = @intCast(wip.extra.items.len),
|
|
.compile_log_text = compile_log_str_index,
|
|
});
|
|
try wip.extra.appendSlice(gpa, @as([]const u32, @ptrCast(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) Allocator.Error!String {
|
|
const gpa = wip.gpa;
|
|
const index: String = @intCast(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) Allocator.Error!String {
|
|
const gpa = wip.gpa;
|
|
const index: String = @intCast(wip.string_bytes.items.len);
|
|
try wip.string_bytes.print(gpa, fmt, args);
|
|
try wip.string_bytes.append(gpa, 0);
|
|
return index;
|
|
}
|
|
|
|
pub fn addRootErrorMessage(wip: *Wip, em: ErrorMessage) Allocator.Error!void {
|
|
try wip.root_list.ensureUnusedCapacity(wip.gpa, 1);
|
|
wip.root_list.appendAssumeCapacity(try addErrorMessage(wip, em));
|
|
}
|
|
|
|
pub fn addRootErrorMessageWithNotes(
|
|
wip: *Wip,
|
|
msg: ErrorMessage,
|
|
notes: []const ErrorMessage,
|
|
) !void {
|
|
try wip.addRootErrorMessage(msg);
|
|
const notes_start = try wip.reserveNotes(@intCast(notes.len));
|
|
for (notes_start.., notes) |i, note| {
|
|
wip.extra.items[i] = @intFromEnum(wip.addErrorMessageAssumeCapacity(note));
|
|
}
|
|
}
|
|
|
|
pub fn addErrorMessage(wip: *Wip, em: ErrorMessage) Allocator.Error!MessageIndex {
|
|
return @enumFromInt(try addExtra(wip, em));
|
|
}
|
|
|
|
pub fn addErrorMessageAssumeCapacity(wip: *Wip, em: ErrorMessage) MessageIndex {
|
|
return @enumFromInt(addExtraAssumeCapacity(wip, em));
|
|
}
|
|
|
|
pub fn addSourceLocation(wip: *Wip, sl: SourceLocation) Allocator.Error!SourceLocationIndex {
|
|
return @enumFromInt(try addExtra(wip, sl));
|
|
}
|
|
|
|
pub fn addReferenceTrace(wip: *Wip, rt: ReferenceTrace) Allocator.Error!void {
|
|
_ = try addExtra(wip, rt);
|
|
}
|
|
|
|
pub fn addBundleAsNotes(wip: *Wip, other: ErrorBundle) Allocator.Error!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(other_list.len)) catch unreachable;
|
|
for (notes_start.., other_list) |note, message| {
|
|
// This line can cause `wip.extra.items` to be resized.
|
|
const note_index = @intFromEnum(wip.addOtherMessage(other, message) catch unreachable);
|
|
wip.extra.items[note] = note_index;
|
|
}
|
|
}
|
|
|
|
pub fn addBundleAsRoots(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();
|
|
|
|
try wip.root_list.ensureUnusedCapacity(gpa, other_list.len);
|
|
for (other_list) |other_msg| {
|
|
// The ensureUnusedCapacity calls above guarantees this.
|
|
wip.root_list.appendAssumeCapacity(wip.addOtherMessage(other, other_msg) 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(wip.extra.items.len - notes_len);
|
|
}
|
|
|
|
pub fn addZirErrorMessages(
|
|
eb: *ErrorBundle.Wip,
|
|
zir: std.zig.Zir,
|
|
tree: std.zig.Ast,
|
|
source: [:0]const u8,
|
|
src_path: []const u8,
|
|
) !void {
|
|
const Zir = std.zig.Zir;
|
|
const payload_index = zir.extra[@intFromEnum(Zir.ExtraIndex.compile_errors)];
|
|
assert(payload_index != 0);
|
|
|
|
const header = zir.extraData(Zir.Inst.CompileErrors, payload_index);
|
|
const items_len = header.data.items_len;
|
|
var extra_index = header.end;
|
|
for (0..items_len) |_| {
|
|
const item = zir.extraData(Zir.Inst.CompileErrors.Item, extra_index);
|
|
extra_index = item.end;
|
|
const err_span = blk: {
|
|
if (item.data.node.unwrap()) |node| {
|
|
break :blk tree.nodeToSpan(node);
|
|
} else if (item.data.token.unwrap()) |token| {
|
|
const start = tree.tokenStart(token) + item.data.byte_offset;
|
|
const end = start + @as(u32, @intCast(tree.tokenSlice(token).len)) - item.data.byte_offset;
|
|
break :blk std.zig.Ast.Span{ .start = start, .end = end, .main = start };
|
|
} else unreachable;
|
|
};
|
|
const err_loc = std.zig.findLineColumn(source, err_span.main);
|
|
|
|
{
|
|
const msg = zir.nullTerminatedString(item.data.msg);
|
|
try eb.addRootErrorMessage(.{
|
|
.msg = try eb.addString(msg),
|
|
.src_loc = try eb.addSourceLocation(.{
|
|
.src_path = try eb.addString(src_path),
|
|
.span_start = err_span.start,
|
|
.span_main = err_span.main,
|
|
.span_end = err_span.end,
|
|
.line = @intCast(err_loc.line),
|
|
.column = @intCast(err_loc.column),
|
|
.source_line = try eb.addString(err_loc.source_line),
|
|
}),
|
|
.notes_len = item.data.notesLen(zir),
|
|
});
|
|
}
|
|
|
|
if (item.data.notes != 0) {
|
|
const notes_start = try eb.reserveNotes(item.data.notesLen(zir));
|
|
const block = zir.extraData(Zir.Inst.Block, item.data.notes);
|
|
const body = zir.extra[block.end..][0..block.data.body_len];
|
|
for (notes_start.., body) |note_i, body_elem| {
|
|
const note_item = zir.extraData(Zir.Inst.CompileErrors.Item, body_elem);
|
|
const msg = zir.nullTerminatedString(note_item.data.msg);
|
|
const span = blk: {
|
|
if (note_item.data.node.unwrap()) |node| {
|
|
break :blk tree.nodeToSpan(node);
|
|
} else if (note_item.data.token.unwrap()) |token| {
|
|
const start = tree.tokenStart(token) + note_item.data.byte_offset;
|
|
const end = start + @as(u32, @intCast(tree.tokenSlice(token).len)) - item.data.byte_offset;
|
|
break :blk std.zig.Ast.Span{ .start = start, .end = end, .main = start };
|
|
} else unreachable;
|
|
};
|
|
const loc = std.zig.findLineColumn(source, span.main);
|
|
|
|
// This line can cause `wip.extra.items` to be resized.
|
|
const note_index = @intFromEnum(try eb.addErrorMessage(.{
|
|
.msg = try eb.addString(msg),
|
|
.src_loc = try eb.addSourceLocation(.{
|
|
.src_path = try eb.addString(src_path),
|
|
.span_start = span.start,
|
|
.span_main = span.main,
|
|
.span_end = span.end,
|
|
.line = @intCast(loc.line),
|
|
.column = @intCast(loc.column),
|
|
.source_line = if (loc.eql(err_loc))
|
|
0
|
|
else
|
|
try eb.addString(loc.source_line),
|
|
}),
|
|
.notes_len = 0, // TODO rework this function to be recursive
|
|
}));
|
|
eb.extra.items[note_i] = note_index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn addZoirErrorMessages(
|
|
eb: *ErrorBundle.Wip,
|
|
zoir: std.zig.Zoir,
|
|
tree: std.zig.Ast,
|
|
source: [:0]const u8,
|
|
src_path: []const u8,
|
|
) !void {
|
|
assert(zoir.hasCompileErrors());
|
|
|
|
for (zoir.compile_errors) |err| {
|
|
const err_span: std.zig.Ast.Span = span: {
|
|
if (err.token.unwrap()) |token| {
|
|
const token_start = tree.tokenStart(token);
|
|
const start = token_start + err.node_or_offset;
|
|
const end = token_start + @as(u32, @intCast(tree.tokenSlice(token).len));
|
|
break :span .{ .start = start, .end = end, .main = start };
|
|
} else {
|
|
break :span tree.nodeToSpan(@enumFromInt(err.node_or_offset));
|
|
}
|
|
};
|
|
const err_loc = std.zig.findLineColumn(source, err_span.main);
|
|
|
|
try eb.addRootErrorMessage(.{
|
|
.msg = try eb.addString(err.msg.get(zoir)),
|
|
.src_loc = try eb.addSourceLocation(.{
|
|
.src_path = try eb.addString(src_path),
|
|
.span_start = err_span.start,
|
|
.span_main = err_span.main,
|
|
.span_end = err_span.end,
|
|
.line = @intCast(err_loc.line),
|
|
.column = @intCast(err_loc.column),
|
|
.source_line = try eb.addString(err_loc.source_line),
|
|
}),
|
|
.notes_len = err.note_count,
|
|
});
|
|
|
|
const notes_start = try eb.reserveNotes(err.note_count);
|
|
for (notes_start.., err.first_note.., 0..err.note_count) |eb_note_idx, zoir_note_idx, _| {
|
|
const note = zoir.error_notes[zoir_note_idx];
|
|
const note_span: std.zig.Ast.Span = span: {
|
|
if (note.token.unwrap()) |token| {
|
|
const token_start = tree.tokenStart(token);
|
|
const start = token_start + note.node_or_offset;
|
|
const end = token_start + @as(u32, @intCast(tree.tokenSlice(token).len));
|
|
break :span .{ .start = start, .end = end, .main = start };
|
|
} else {
|
|
break :span tree.nodeToSpan(@enumFromInt(note.node_or_offset));
|
|
}
|
|
};
|
|
const note_loc = std.zig.findLineColumn(source, note_span.main);
|
|
|
|
// This line can cause `wip.extra.items` to be resized.
|
|
const note_index = @intFromEnum(try eb.addErrorMessage(.{
|
|
.msg = try eb.addString(note.msg.get(zoir)),
|
|
.src_loc = try eb.addSourceLocation(.{
|
|
.src_path = try eb.addString(src_path),
|
|
.span_start = note_span.start,
|
|
.span_main = note_span.main,
|
|
.span_end = note_span.end,
|
|
.line = @intCast(note_loc.line),
|
|
.column = @intCast(note_loc.column),
|
|
.source_line = if (note_loc.eql(err_loc))
|
|
0
|
|
else
|
|
try eb.addString(note_loc.source_line),
|
|
}),
|
|
.notes_len = 0,
|
|
}));
|
|
eb.extra.items[eb_note_idx] = note_index;
|
|
}
|
|
}
|
|
}
|
|
|
|
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] = @intFromEnum(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);
|
|
|
|
var ref_traces: std.ArrayList(ReferenceTrace) = .empty;
|
|
defer ref_traces.deinit(wip.gpa);
|
|
|
|
if (other_sl.reference_trace_len > 0) {
|
|
var ref_index = other.extraData(SourceLocation, @intFromEnum(index)).end;
|
|
for (0..other_sl.reference_trace_len) |_| {
|
|
const other_ref_trace_ed = other.extraData(ReferenceTrace, ref_index);
|
|
const other_ref_trace = other_ref_trace_ed.data;
|
|
ref_index = other_ref_trace_ed.end;
|
|
|
|
const ref_trace: ReferenceTrace = if (other_ref_trace.src_loc == .none) .{
|
|
// sentinel ReferenceTrace does not store a string index in decl_name
|
|
.decl_name = other_ref_trace.decl_name,
|
|
.src_loc = .none,
|
|
} else .{
|
|
.decl_name = try wip.addString(other.nullTerminatedString(other_ref_trace.decl_name)),
|
|
.src_loc = try wip.addOtherSourceLocation(other, other_ref_trace.src_loc),
|
|
};
|
|
try ref_traces.append(wip.gpa, ref_trace);
|
|
}
|
|
}
|
|
|
|
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 = if (other_sl.source_line != 0)
|
|
try wip.addString(other.nullTerminatedString(other_sl.source_line))
|
|
else
|
|
0,
|
|
.reference_trace_len = other_sl.reference_trace_len,
|
|
});
|
|
|
|
for (ref_traces.items) |ref_trace| {
|
|
try wip.addReferenceTrace(ref_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: u32 = @intCast(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 => @intFromEnum(@field(extra, field.name)),
|
|
SourceLocationIndex => @intFromEnum(@field(extra, field.name)),
|
|
else => @compileError("bad field type"),
|
|
};
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
test addBundleAsRoots {
|
|
var bundle = bundle: {
|
|
var wip: ErrorBundle.Wip = undefined;
|
|
try wip.init(std.testing.allocator);
|
|
errdefer wip.deinit();
|
|
|
|
var ref_traces: [3]ReferenceTrace = undefined;
|
|
for (&ref_traces, 0..) |*ref_trace, i| {
|
|
if (i == ref_traces.len - 1) {
|
|
// sentinel reference trace
|
|
ref_trace.* = .{
|
|
.decl_name = 3, // signifies 3 hidden references
|
|
.src_loc = .none,
|
|
};
|
|
} else {
|
|
ref_trace.* = .{
|
|
.decl_name = try wip.addString("foo"),
|
|
.src_loc = try wip.addSourceLocation(.{
|
|
.src_path = try wip.addString("foo"),
|
|
.line = 1,
|
|
.column = 2,
|
|
.span_start = 3,
|
|
.span_main = 4,
|
|
.span_end = 5,
|
|
.source_line = 0,
|
|
}),
|
|
};
|
|
}
|
|
}
|
|
|
|
const src_loc = try wip.addSourceLocation(.{
|
|
.src_path = try wip.addString("foo"),
|
|
.line = 1,
|
|
.column = 2,
|
|
.span_start = 3,
|
|
.span_main = 4,
|
|
.span_end = 5,
|
|
.source_line = try wip.addString("some source code"),
|
|
.reference_trace_len = ref_traces.len,
|
|
});
|
|
for (&ref_traces) |ref_trace| {
|
|
try wip.addReferenceTrace(ref_trace);
|
|
}
|
|
|
|
try wip.addRootErrorMessage(ErrorMessage{
|
|
.msg = try wip.addString("hello world"),
|
|
.src_loc = src_loc,
|
|
.notes_len = 1,
|
|
});
|
|
const i = try wip.reserveNotes(1);
|
|
const note_index = @intFromEnum(wip.addErrorMessageAssumeCapacity(.{
|
|
.msg = try wip.addString("this is a note"),
|
|
.src_loc = try wip.addSourceLocation(.{
|
|
.src_path = try wip.addString("bar"),
|
|
.line = 1,
|
|
.column = 2,
|
|
.span_start = 3,
|
|
.span_main = 4,
|
|
.span_end = 5,
|
|
.source_line = try wip.addString("another line of source"),
|
|
}),
|
|
}));
|
|
wip.extra.items[i] = note_index;
|
|
|
|
break :bundle try wip.toOwnedBundle("");
|
|
};
|
|
defer bundle.deinit(std.testing.allocator);
|
|
|
|
const ttyconf: Io.tty.Config = .no_color;
|
|
|
|
var bundle_buf: Writer.Allocating = .init(std.testing.allocator);
|
|
const bundle_bw = &bundle_buf.interface;
|
|
defer bundle_buf.deinit();
|
|
try bundle.renderToWriter(.{ .ttyconf = ttyconf }, bundle_bw);
|
|
|
|
var copy = copy: {
|
|
var wip: ErrorBundle.Wip = undefined;
|
|
try wip.init(std.testing.allocator);
|
|
errdefer wip.deinit();
|
|
|
|
try wip.addBundleAsRoots(bundle);
|
|
|
|
break :copy try wip.toOwnedBundle("");
|
|
};
|
|
defer copy.deinit(std.testing.allocator);
|
|
|
|
var copy_buf: Writer.Allocating = .init(std.testing.allocator);
|
|
const copy_bw = ©_buf.interface;
|
|
defer copy_buf.deinit();
|
|
try copy.renderToWriter(.{ .ttyconf = ttyconf }, copy_bw);
|
|
|
|
try std.testing.expectEqualStrings(bundle_bw.written(), copy_bw.written());
|
|
}
|
|
};
|