authorgravatar for r00ster91@proton.meWooster <r00ster91@proton.me> 2022-07-11 23:10:39+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-07-12 00:10:39+03:00
logda75eb0d7909b53aea78cbe5c9165f6621ccc9fa
tree8a43591a2bc76e1a92698d7e1d899f1d3ddb13a1
parentade9bd9287fcdd730a7562811d0e50a6899a6cf2
signature Signed by PGP key 4AEE18F83AFDEB23

Compilation: indent multiline error messages properly

Co-authored-by: Veikka Tuominen <git@vexu.eu>

3 files changed, 84 insertions(+), 14 deletions(-)

src/Compilation.zig+34-13
......@@ -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+14-1
......@@ -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+36
......@@ -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 {