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 {...@@ -344,6 +344,20 @@ pub const AllErrors = struct {
344 /// Does not include the trailing newline.344 /// Does not include the trailing newline.
345 source_line: ?[]const u8,345 source_line: ?[]const u8,
346 notes: []Message = &.{},346 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 }
347 },361 },
348 plain: struct {362 plain: struct {
349 msg: []const u8,363 msg: []const u8,
...@@ -367,35 +381,41 @@ pub const AllErrors = struct {...@@ -367,35 +381,41 @@ pub const AllErrors = struct {
367 std.debug.getStderrMutex().lock();381 std.debug.getStderrMutex().lock();
368 defer std.debug.getStderrMutex().unlock();382 defer std.debug.getStderrMutex().unlock();
369 const stderr = std.io.getStdErr();383 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;
371 }385 }
372386
373 fn renderToStdErrInner(387 pub fn renderToWriter(
374 msg: Message,388 msg: Message,
375 ttyconf: std.debug.TTY.Config,389 ttyconf: std.debug.TTY.Config,
376 stderr_file: std.fs.File,390 stderr: anytype,
377 kind: []const u8,391 kind: []const u8,
378 color: std.debug.TTY.Color,392 color: std.debug.TTY.Color,
379 indent: usize,393 indent: usize,
380 ) anyerror!void {394 ) anyerror!void {
381 const stderr = stderr_file.writer();395 var counting_writer = std.io.countingWriter(stderr);
396 const counting_stderr = counting_writer.writer();
382 switch (msg) {397 switch (msg) {
383 .src => |src| {398 .src => |src| {
384 try stderr.writeByteNTimes(' ', indent);399 try counting_stderr.writeByteNTimes(' ', indent);
385 ttyconf.setColor(stderr, .Bold);400 ttyconf.setColor(stderr, .Bold);
386 try stderr.print("{s}:{d}:{d}: ", .{401 try counting_stderr.print("{s}:{d}:{d}: ", .{
387 src.src_path,402 src.src_path,
388 src.line + 1,403 src.line + 1,
389 src.column + 1,404 src.column + 1,
390 });405 });
391 ttyconf.setColor(stderr, color);406 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);
393 ttyconf.setColor(stderr, .Reset);412 ttyconf.setColor(stderr, .Reset);
394 ttyconf.setColor(stderr, .Bold);413 ttyconf.setColor(stderr, .Bold);
395 if (src.count == 1) {414 if (src.count == 1) {
396 try stderr.print(" {s}\n", .{src.msg});415 try src.writeMsg(stderr, prefix_len);
416 try stderr.writeByte('\n');
397 } else {417 } else {
398 try stderr.print(" {s}", .{src.msg});418 try src.writeMsg(stderr, prefix_len);
399 ttyconf.setColor(stderr, .Dim);419 ttyconf.setColor(stderr, .Dim);
400 try stderr.print(" ({d} times)\n", .{src.count});420 try stderr.print(" ({d} times)\n", .{src.count});
401 }421 }
...@@ -414,24 +434,25 @@ pub const AllErrors = struct {...@@ -414,24 +434,25 @@ pub const AllErrors = struct {
414 }434 }
415 }435 }
416 for (src.notes) |note| {436 for (src.notes) |note| {
417 try note.renderToStdErrInner(ttyconf, stderr_file, "note:", .Cyan, indent);437 try note.renderToWriter(ttyconf, stderr, "note", .Cyan, indent);
418 }438 }
419 },439 },
420 .plain => |plain| {440 .plain => |plain| {
421 ttyconf.setColor(stderr, color);441 ttyconf.setColor(stderr, color);
422 try stderr.writeByteNTimes(' ', indent);442 try stderr.writeByteNTimes(' ', indent);
423 try stderr.writeAll(kind);443 try stderr.writeAll(kind);
444 try stderr.writeAll(": ");
424 ttyconf.setColor(stderr, .Reset);445 ttyconf.setColor(stderr, .Reset);
425 if (plain.count == 1) {446 if (plain.count == 1) {
426 try stderr.print(" {s}\n", .{plain.msg});447 try stderr.print("{s}\n", .{plain.msg});
427 } else {448 } else {
428 try stderr.print(" {s}", .{plain.msg});449 try stderr.print("{s}", .{plain.msg});
429 ttyconf.setColor(stderr, .Dim);450 ttyconf.setColor(stderr, .Dim);
430 try stderr.print(" ({d} times)\n", .{plain.count});451 try stderr.print(" ({d} times)\n", .{plain.count});
431 }452 }
432 ttyconf.setColor(stderr, .Reset);453 ttyconf.setColor(stderr, .Reset);
433 for (plain.notes) |note| {454 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);
435 }456 }
436 },457 },
437 }458 }
src/test.zig+14-1
...@@ -1690,12 +1690,25 @@ pub const TestContext = struct {...@@ -1690,12 +1690,25 @@ pub const TestContext = struct {
1690 tmp_dir_path_plus_slash,1690 tmp_dir_path_plus_slash,
1691 );1691 );
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
1693 if (src_path_ok and1706 if (src_path_ok and
1694 (case_msg.src.line == std.math.maxInt(u32) or1707 (case_msg.src.line == std.math.maxInt(u32) or
1695 actual_msg.line == case_msg.src.line) and1708 actual_msg.line == case_msg.src.line) and
1696 (case_msg.src.column == std.math.maxInt(u32) or1709 (case_msg.src.column == std.math.maxInt(u32) or
1697 actual_msg.column == case_msg.src.column) and1710 actual_msg.column == case_msg.src.column) and
1698 std.mem.eql(u8, expected_msg, actual_msg.msg) and1711 std.mem.eql(u8, expected_msg, rendered_msg) and
1699 case_msg.src.kind == .@"error" and1712 case_msg.src.kind == .@"error" and
1700 actual_msg.count == case_msg.src.count)1713 actual_msg.count == case_msg.src.count)
1701 {1714 {
test/compile_errors.zig+36
...@@ -138,6 +138,42 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -138,6 +138,42 @@ pub fn addCases(ctx: *TestContext) !void {
138 "tmp.zig:2:1: error: invalid character: '\\t'",138 "tmp.zig:2:1: error: invalid character: '\\t'",
139 });139 });
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
141 // TODO test this in stage2, but we won't even try in stage1177 // TODO test this in stage2, but we won't even try in stage1
142 //ctx.objErrStage1("inline fn calls itself indirectly",178 //ctx.objErrStage1("inline fn calls itself indirectly",
143 // \\export fn foo() void {179 // \\export fn foo() void {