Skip to content

Commit da75eb0

Browse files
wooster0Vexu
andauthored
Compilation: indent multiline error messages properly
Co-authored-by: Veikka Tuominen <[email protected]>
1 parent ade9bd9 commit da75eb0

File tree

3 files changed

+84
-14
lines changed

3 files changed

+84
-14
lines changed

src/Compilation.zig

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,20 @@ pub const AllErrors = struct {
344344
/// Does not include the trailing newline.
345345
source_line: ?[]const u8,
346346
notes: []Message = &.{},
347+
348+
/// Splits the error message up into lines to properly indent them
349+
/// to allow for long, good-looking error messages.
350+
///
351+
/// This is used to split the message in `@compileError("hello\nworld")` for example.
352+
fn writeMsg(src: @This(), stderr: anytype, indent: usize) !void {
353+
var lines = mem.split(u8, src.msg, "\n");
354+
while (lines.next()) |line| {
355+
try stderr.writeAll(line);
356+
if (lines.index == null) break;
357+
try stderr.writeByte('\n');
358+
try stderr.writeByteNTimes(' ', indent);
359+
}
360+
}
347361
},
348362
plain: struct {
349363
msg: []const u8,
@@ -367,35 +381,41 @@ pub const AllErrors = struct {
367381
std.debug.getStderrMutex().lock();
368382
defer std.debug.getStderrMutex().unlock();
369383
const stderr = std.io.getStdErr();
370-
return msg.renderToStdErrInner(ttyconf, stderr, "error:", .Red, 0) catch return;
384+
return msg.renderToWriter(ttyconf, stderr.writer(), "error", .Red, 0) catch return;
371385
}
372386

373-
fn renderToStdErrInner(
387+
pub fn renderToWriter(
374388
msg: Message,
375389
ttyconf: std.debug.TTY.Config,
376-
stderr_file: std.fs.File,
390+
stderr: anytype,
377391
kind: []const u8,
378392
color: std.debug.TTY.Color,
379393
indent: usize,
380394
) anyerror!void {
381-
const stderr = stderr_file.writer();
395+
var counting_writer = std.io.countingWriter(stderr);
396+
const counting_stderr = counting_writer.writer();
382397
switch (msg) {
383398
.src => |src| {
384-
try stderr.writeByteNTimes(' ', indent);
399+
try counting_stderr.writeByteNTimes(' ', indent);
385400
ttyconf.setColor(stderr, .Bold);
386-
try stderr.print("{s}:{d}:{d}: ", .{
401+
try counting_stderr.print("{s}:{d}:{d}: ", .{
387402
src.src_path,
388403
src.line + 1,
389404
src.column + 1,
390405
});
391406
ttyconf.setColor(stderr, color);
392-
try stderr.writeAll(kind);
407+
try counting_stderr.writeAll(kind);
408+
try counting_stderr.writeAll(": ");
409+
// This is the length of the part before the error message:
410+
// e.g. "file.zig:4:5: error: "
411+
const prefix_len = @intCast(usize, counting_stderr.context.bytes_written);
393412
ttyconf.setColor(stderr, .Reset);
394413
ttyconf.setColor(stderr, .Bold);
395414
if (src.count == 1) {
396-
try stderr.print(" {s}\n", .{src.msg});
415+
try src.writeMsg(stderr, prefix_len);
416+
try stderr.writeByte('\n');
397417
} else {
398-
try stderr.print(" {s}", .{src.msg});
418+
try src.writeMsg(stderr, prefix_len);
399419
ttyconf.setColor(stderr, .Dim);
400420
try stderr.print(" ({d} times)\n", .{src.count});
401421
}
@@ -414,24 +434,25 @@ pub const AllErrors = struct {
414434
}
415435
}
416436
for (src.notes) |note| {
417-
try note.renderToStdErrInner(ttyconf, stderr_file, "note:", .Cyan, indent);
437+
try note.renderToWriter(ttyconf, stderr, "note", .Cyan, indent);
418438
}
419439
},
420440
.plain => |plain| {
421441
ttyconf.setColor(stderr, color);
422442
try stderr.writeByteNTimes(' ', indent);
423443
try stderr.writeAll(kind);
444+
try stderr.writeAll(": ");
424445
ttyconf.setColor(stderr, .Reset);
425446
if (plain.count == 1) {
426-
try stderr.print(" {s}\n", .{plain.msg});
447+
try stderr.print("{s}\n", .{plain.msg});
427448
} else {
428-
try stderr.print(" {s}", .{plain.msg});
449+
try stderr.print("{s}", .{plain.msg});
429450
ttyconf.setColor(stderr, .Dim);
430451
try stderr.print(" ({d} times)\n", .{plain.count});
431452
}
432453
ttyconf.setColor(stderr, .Reset);
433454
for (plain.notes) |note| {
434-
try note.renderToStdErrInner(ttyconf, stderr_file, "error:", .Red, indent + 4);
455+
try note.renderToWriter(ttyconf, stderr, "error", .Red, indent + 4);
435456
}
436457
},
437458
}

src/test.zig

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1690,12 +1690,25 @@ pub const TestContext = struct {
16901690
tmp_dir_path_plus_slash,
16911691
);
16921692

1693+
var buf: [1024]u8 = undefined;
1694+
const rendered_msg = blk: {
1695+
var msg: Compilation.AllErrors.Message = actual_error;
1696+
msg.src.src_path = case_msg.src.src_path;
1697+
msg.src.notes = &.{};
1698+
var fib = std.io.fixedBufferStream(&buf);
1699+
try msg.renderToWriter(.no_color, fib.writer(), "error", .Red, 0);
1700+
var it = std.mem.split(u8, fib.getWritten(), "error: ");
1701+
_ = it.next();
1702+
const rendered = it.rest();
1703+
break :blk rendered[0 .. rendered.len - 1]; // trim final newline
1704+
};
1705+
16931706
if (src_path_ok and
16941707
(case_msg.src.line == std.math.maxInt(u32) or
16951708
actual_msg.line == case_msg.src.line) and
16961709
(case_msg.src.column == std.math.maxInt(u32) or
16971710
actual_msg.column == case_msg.src.column) and
1698-
std.mem.eql(u8, expected_msg, actual_msg.msg) and
1711+
std.mem.eql(u8, expected_msg, rendered_msg) and
16991712
case_msg.src.kind == .@"error" and
17001713
actual_msg.count == case_msg.src.count)
17011714
{

test/compile_errors.zig

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,42 @@ pub fn addCases(ctx: *TestContext) !void {
138138
"tmp.zig:2:1: error: invalid character: '\\t'",
139139
});
140140

141+
{
142+
const case = ctx.obj("multiline error messages", .{});
143+
case.backend = .stage2;
144+
145+
case.addError(
146+
\\comptime {
147+
\\ @compileError("hello\nworld");
148+
\\}
149+
, &[_][]const u8{
150+
\\:2:5: error: hello
151+
\\ world
152+
});
153+
154+
case.addError(
155+
\\comptime {
156+
\\ @compileError(
157+
\\ \\
158+
\\ \\hello!
159+
\\ \\I'm a multiline error message.
160+
\\ \\I hope to be very useful!
161+
\\ \\
162+
\\ \\also I will leave this trailing newline here if you don't mind
163+
\\ \\
164+
\\ );
165+
\\}
166+
, &[_][]const u8{
167+
\\:2:5: error:
168+
\\ hello!
169+
\\ I'm a multiline error message.
170+
\\ I hope to be very useful!
171+
\\
172+
\\ also I will leave this trailing newline here if you don't mind
173+
\\
174+
});
175+
}
176+
141177
// TODO test this in stage2, but we won't even try in stage1
142178
//ctx.objErrStage1("inline fn calls itself indirectly",
143179
// \\export fn foo() void {

0 commit comments

Comments
 (0)