struct Wip [src]
Fields
gpa: Allocator
string_bytes: std.ArrayListUnmanaged(u8)
extra: std.ArrayListUnmanaged(u32)The first thing in this array is a ErrorMessageList.
root_list: std.ArrayListUnmanaged(MessageIndex)
Members
- addBundleAsNotes (Function)
- addBundleAsRoots (Function)
- addErrorMessage (Function)
- addErrorMessageAssumeCapacity (Function)
- addReferenceTrace (Function)
- addRootErrorMessage (Function)
- addSourceLocation (Function)
- addString (Function)
- addZirErrorMessages (Function)
- addZoirErrorMessages (Function)
- deinit (Function)
- init (Function)
- printString (Function)
- reserveNotes (Function)
- tmpBundle (Function)
- toOwnedBundle (Function)
Source
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: 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.writer(gpa).print(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 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.ArrayListUnmanaged(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: std.io.tty.Config = .no_color;
var bundle_buf = std.ArrayList(u8).init(std.testing.allocator);
defer bundle_buf.deinit();
try bundle.renderToWriter(.{ .ttyconf = ttyconf }, bundle_buf.writer());
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 = std.ArrayList(u8).init(std.testing.allocator);
defer copy_buf.deinit();
try copy.renderToWriter(.{ .ttyconf = ttyconf }, copy_buf.writer());
try std.testing.expectEqualStrings(bundle_buf.items, copy_buf.items);
}
}