Commit da75eb0d79
Changed files (3)
src/Compilation.zig
@@ -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,24 +434,25 @@ 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});
+ try stderr.print("{s}\n", .{plain.msg});
} else {
- try stderr.print(" {s}", .{plain.msg});
+ try stderr.print("{s}", .{plain.msg});
ttyconf.setColor(stderr, .Dim);
try stderr.print(" ({d} times)\n", .{plain.count});
}
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);
}
},
}
src/test.zig
@@ -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)
{
test/compile_errors.zig
@@ -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 {