Compilation: indent multiline error messages properly

Co-authored-by: Veikka Tuominen <git@vexu.eu>
This commit is contained in:
r00ster91 2022-07-11 23:10:39 +02:00 committed by GitHub
parent ade9bd9287
commit da75eb0d79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 14 deletions

View file

@ -344,6 +344,20 @@ pub const AllErrors = struct {
/// Does not include the trailing newline.
source_line: ?[]const u8,
notes: []Message = &.{},
/// 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(src: @This(), stderr: anytype, indent: usize) !void {
var lines = mem.split(u8, src.msg, "\n");
while (lines.next()) |line| {
try stderr.writeAll(line);
if (lines.index == null) break;
try stderr.writeByte('\n');
try stderr.writeByteNTimes(' ', indent);
}
}
},
plain: struct {
msg: []const u8,
@ -367,35 +381,41 @@ pub const AllErrors = struct {
std.debug.getStderrMutex().lock();
defer std.debug.getStderrMutex().unlock();
const stderr = std.io.getStdErr();
return msg.renderToStdErrInner(ttyconf, stderr, "error:", .Red, 0) catch return;
return msg.renderToWriter(ttyconf, stderr.writer(), "error", .Red, 0) catch return;
}
fn renderToStdErrInner(
pub fn renderToWriter(
msg: Message,
ttyconf: std.debug.TTY.Config,
stderr_file: std.fs.File,
stderr: anytype,
kind: []const u8,
color: std.debug.TTY.Color,
indent: usize,
) anyerror!void {
const stderr = stderr_file.writer();
var counting_writer = std.io.countingWriter(stderr);
const counting_stderr = counting_writer.writer();
switch (msg) {
.src => |src| {
try stderr.writeByteNTimes(' ', indent);
try counting_stderr.writeByteNTimes(' ', indent);
ttyconf.setColor(stderr, .Bold);
try stderr.print("{s}:{d}:{d}: ", .{
try counting_stderr.print("{s}:{d}:{d}: ", .{
src.src_path,
src.line + 1,
src.column + 1,
});
ttyconf.setColor(stderr, color);
try stderr.writeAll(kind);
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);
ttyconf.setColor(stderr, .Reset);
ttyconf.setColor(stderr, .Bold);
if (src.count == 1) {
try stderr.print(" {s}\n", .{src.msg});
try src.writeMsg(stderr, prefix_len);
try stderr.writeByte('\n');
} else {
try stderr.print(" {s}", .{src.msg});
try src.writeMsg(stderr, prefix_len);
ttyconf.setColor(stderr, .Dim);
try stderr.print(" ({d} times)\n", .{src.count});
}
@ -414,13 +434,14 @@ pub const AllErrors = struct {
}
}
for (src.notes) |note| {
try note.renderToStdErrInner(ttyconf, stderr_file, "note:", .Cyan, indent);
try note.renderToWriter(ttyconf, stderr, "note", .Cyan, indent);
}
},
.plain => |plain| {
ttyconf.setColor(stderr, color);
try stderr.writeByteNTimes(' ', indent);
try stderr.writeAll(kind);
try stderr.writeAll(": ");
ttyconf.setColor(stderr, .Reset);
if (plain.count == 1) {
try stderr.print("{s}\n", .{plain.msg});
@ -431,7 +452,7 @@ pub const AllErrors = struct {
}
ttyconf.setColor(stderr, .Reset);
for (plain.notes) |note| {
try note.renderToStdErrInner(ttyconf, stderr_file, "error:", .Red, indent + 4);
try note.renderToWriter(ttyconf, stderr, "error", .Red, indent + 4);
}
},
}

View file

@ -1690,12 +1690,25 @@ pub const TestContext = struct {
tmp_dir_path_plus_slash,
);
var buf: [1024]u8 = undefined;
const rendered_msg = blk: {
var msg: Compilation.AllErrors.Message = actual_error;
msg.src.src_path = case_msg.src.src_path;
msg.src.notes = &.{};
var fib = std.io.fixedBufferStream(&buf);
try msg.renderToWriter(.no_color, fib.writer(), "error", .Red, 0);
var it = std.mem.split(u8, fib.getWritten(), "error: ");
_ = it.next();
const rendered = it.rest();
break :blk rendered[0 .. rendered.len - 1]; // trim final newline
};
if (src_path_ok and
(case_msg.src.line == std.math.maxInt(u32) or
actual_msg.line == case_msg.src.line) and
(case_msg.src.column == std.math.maxInt(u32) or
actual_msg.column == case_msg.src.column) and
std.mem.eql(u8, expected_msg, actual_msg.msg) and
std.mem.eql(u8, expected_msg, rendered_msg) and
case_msg.src.kind == .@"error" and
actual_msg.count == case_msg.src.count)
{

View file

@ -138,6 +138,42 @@ pub fn addCases(ctx: *TestContext) !void {
"tmp.zig:2:1: error: invalid character: '\\t'",
});
{
const case = ctx.obj("multiline error messages", .{});
case.backend = .stage2;
case.addError(
\\comptime {
\\ @compileError("hello\nworld");
\\}
, &[_][]const u8{
\\:2:5: error: hello
\\ world
});
case.addError(
\\comptime {
\\ @compileError(
\\ \\
\\ \\hello!
\\ \\I'm a multiline error message.
\\ \\I hope to be very useful!
\\ \\
\\ \\also I will leave this trailing newline here if you don't mind
\\ \\
\\ );
\\}
, &[_][]const u8{
\\:2:5: error:
\\ hello!
\\ I'm a multiline error message.
\\ I hope to be very useful!
\\
\\ also I will leave this trailing newline here if you don't mind
\\
});
}
// TODO test this in stage2, but we won't even try in stage1
//ctx.objErrStage1("inline fn calls itself indirectly",
// \\export fn foo() void {