authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-25 09:32:40+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-25 09:32:40+00:00
logfcf8d5ada28c02722575cc78d41171692a227061
tree038d8ee507ce30836196db0bd951e3b18b8fcc2e
parent7ef345f342534f93a7e61d9ecd096523f048871d
signaturelock-open Commit is signed but in an unrecognized format.

incr-check: check compile errors against expected

Also modifies all incremental cases using `#expect_error` to include the errors and notes which are expected.

13 files changed, 162 insertions(+), 35 deletions(-)

test/incremental/add_decl+1-1
...@@ -39,7 +39,7 @@ pub fn main() !void {...@@ -39,7 +39,7 @@ pub fn main() !void {
39}39}
40const foo = "good morning\n";40const foo = "good morning\n";
41const bar = "good evening\n";41const bar = "good evening\n";
42#expect_error=ignored42#expect_error=main.zig:3:37: error: use of undeclared identifier 'qux'
4343
44#update=add missing declaration44#update=add missing declaration
45#file=main.zig45#file=main.zig
test/incremental/add_decl_namespaced+2-1
...@@ -39,7 +39,8 @@ pub fn main() !void {...@@ -39,7 +39,8 @@ pub fn main() !void {
39}39}
40const foo = "good morning\n";40const foo = "good morning\n";
41const bar = "good evening\n";41const bar = "good evening\n";
42#expect_error=ignored42#expect_error=main.zig:3:44: error: root source file struct 'main' has no member named 'qux'
43#expect_error=main.zig:1:1: note: struct declared here
4344
44#update=add missing declaration45#update=add missing declaration
45#file=main.zig46#file=main.zig
test/incremental/change_embed_file+2-2
...@@ -20,7 +20,7 @@ Hello again, World!...@@ -20,7 +20,7 @@ Hello again, World!
2020
21#update=delete file21#update=delete file
22#rm_file=string.txt22#rm_file=string.txt
23#expect_error=ignored23#expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound
2424
25#update=remove reference to file25#update=remove reference to file
26#file=main.zig26#file=main.zig
...@@ -38,7 +38,7 @@ const string = @embedFile("string.txt");...@@ -38,7 +38,7 @@ const string = @embedFile("string.txt");
38pub fn main() !void {38pub fn main() !void {
39 try std.io.getStdOut().writeAll(string);39 try std.io.getStdOut().writeAll(string);
40}40}
41#expect_error=ignore41#expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound
4242
43#update=recreate file43#update=recreate file
44#file=string.txt44#file=string.txt
test/incremental/change_enum_tag_type+1-1
...@@ -39,7 +39,7 @@ comptime {...@@ -39,7 +39,7 @@ comptime {
39 std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag);39 std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag);
40}40}
41const std = @import("std");41const std = @import("std");
42#expect_error=ignored42#expect_error=main.zig:7:5: error: enumeration value '4' too large for type 'u2'
43#update=increase tag size43#update=increase tag size
44#file=main.zig44#file=main.zig
45const Tag = u3;45const Tag = u3;
test/incremental/compile_error_then_log+5-2
...@@ -4,19 +4,22 @@...@@ -4,19 +4,22 @@
4#target=wasm32-wasi-selfhosted4#target=wasm32-wasi-selfhosted
5#update=initial version with compile error5#update=initial version with compile error
6#file=main.zig6#file=main.zig
7pub fn main() void {}
7comptime {8comptime {
8 @compileError("this is an error");9 @compileError("this is an error");
9}10}
10comptime {11comptime {
11 @compileLog("this is a log");12 @compileLog("this is a log");
12}13}
13#expect_error=ignored14#expect_error=main.zig:3:5: error: this is an error
15
14#update=remove the compile error16#update=remove the compile error
15#file=main.zig17#file=main.zig
18pub fn main() void {}
16comptime {19comptime {
17 //@compileError("this is an error");20 //@compileError("this is an error");
18}21}
19comptime {22comptime {
20 @compileLog("this is a log");23 @compileLog("this is a log");
21}24}
22#expect_error=ignored25#expect_error=main.zig:6:5: error: found compile log statement
test/incremental/delete_comptime_decls+5-2
...@@ -26,7 +26,10 @@ comptime {...@@ -26,7 +26,10 @@ comptime {
26 const slice = array[3..2];26 const slice = array[3..2];
27 _ = slice;27 _ = slice;
28}28}
29#expect_error=ignored29#expect_error=main.zig:5:32: error: end index 6 out of bounds for slice of length 4 +1 (sentinel)
30#expect_error=main.zig:10:28: error: end index 6 out of bounds for array of length 4 +1 (sentinel)
31#expect_error=main.zig:15:28: error: end index 5 out of bounds for array of length 4
32#expect_error=main.zig:20:25: error: start index 3 is larger than end index 2
3033
31#update=delete and modify comptime decls34#update=delete and modify comptime decls
32#file=main.zig35#file=main.zig
...@@ -38,4 +41,4 @@ comptime {...@@ -38,4 +41,4 @@ comptime {
38 const y = x[0..runtime_len];41 const y = x[0..runtime_len];
39 _ = y;42 _ = y;
40}43}
41#expect_error=ignored44#expect_error=main.zig:6:16: error: slice of null pointer
test/incremental/fix_astgen_failure+2-2
...@@ -11,7 +11,7 @@ pub fn main() !void {...@@ -11,7 +11,7 @@ pub fn main() !void {
11pub fn hello() !void {11pub fn hello() !void {
12 try std.io.getStdOut().writeAll("Hello, World!\n");12 try std.io.getStdOut().writeAll("Hello, World!\n");
13}13}
14#expect_error=ignored14#expect_error=foo.zig:2:9: error: use of undeclared identifier 'std'
15#update=fix the error15#update=fix the error
16#file=foo.zig16#file=foo.zig
17const std = @import("std");17const std = @import("std");
...@@ -25,7 +25,7 @@ const std = @import("std");...@@ -25,7 +25,7 @@ const std = @import("std");
25pub fn hello() !void {25pub fn hello() !void {
26 try std.io.getStdOut().writeAll(hello_str);26 try std.io.getStdOut().writeAll(hello_str);
27}27}
28#expect_error=ignored28#expect_error=foo.zig:3:37: error: use of undeclared identifier 'hello_str'
29#update=fix the new error29#update=fix the new error
30#file=foo.zig30#file=foo.zig
31const std = @import("std");31const std = @import("std");
test/incremental/fix_many_errors+20-1
...@@ -24,7 +24,26 @@ export fn f6() void { @compileError("f6"); }...@@ -24,7 +24,26 @@ export fn f6() void { @compileError("f6"); }
24export fn f7() void { @compileError("f7"); }24export fn f7() void { @compileError("f7"); }
25export fn f8() void { @compileError("f8"); }25export fn f8() void { @compileError("f8"); }
26export fn f9() void { @compileError("f9"); }26export fn f9() void { @compileError("f9"); }
27#expect_error=ignored27#expect_error=main.zig:2:12: error: c0
28#expect_error=main.zig:3:12: error: c1
29#expect_error=main.zig:4:12: error: c2
30#expect_error=main.zig:5:12: error: c3
31#expect_error=main.zig:6:12: error: c4
32#expect_error=main.zig:7:12: error: c5
33#expect_error=main.zig:8:12: error: c6
34#expect_error=main.zig:9:12: error: c7
35#expect_error=main.zig:10:12: error: c8
36#expect_error=main.zig:11:12: error: c9
37#expect_error=main.zig:12:23: error: f0
38#expect_error=main.zig:13:23: error: f1
39#expect_error=main.zig:14:23: error: f2
40#expect_error=main.zig:15:23: error: f3
41#expect_error=main.zig:16:23: error: f4
42#expect_error=main.zig:17:23: error: f5
43#expect_error=main.zig:18:23: error: f6
44#expect_error=main.zig:19:23: error: f7
45#expect_error=main.zig:20:23: error: f8
46#expect_error=main.zig:21:23: error: f9
28#update=fix all the errors47#update=fix all the errors
29#file=main.zig48#file=main.zig
30pub fn main() !void {}49pub fn main() !void {}
test/incremental/remove_enum_field+2-1
...@@ -23,4 +23,5 @@ pub fn main() !void {...@@ -23,4 +23,5 @@ pub fn main() !void {
23 try std.io.getStdOut().writer().print("{}\n", .{@intFromEnum(MyEnum.foo)});23 try std.io.getStdOut().writer().print("{}\n", .{@intFromEnum(MyEnum.foo)});
24}24}
25const std = @import("std");25const std = @import("std");
26#expect_error=ignored26#expect_error=main.zig:6:73: error: enum 'main.MyEnum' has no member named 'foo'
27#expect_error=main.zig:1:16: note: enum declared here
test/incremental/remove_invalid_union_backing_enum+2-1
...@@ -15,7 +15,8 @@ pub fn main() void {...@@ -15,7 +15,8 @@ pub fn main() void {
15 const u: U = .{ .a = 123 };15 const u: U = .{ .a = 123 };
16 _ = u;16 _ = u;
17}17}
18#expect_error=ignored18#expect_error=main.zig:6:5: error: no field named 'd' in enum 'main.E'
19#expect_error=main.zig:1:11: note: enum declared here
19#update=remove invalid backing enum20#update=remove invalid backing enum
20#file=main.zig21#file=main.zig
21const U = union {22const U = union {
test/incremental/temporary_parse_error+1-1
...@@ -11,7 +11,7 @@ pub fn main() !void {}...@@ -11,7 +11,7 @@ pub fn main() !void {}
11#update=introduce parse error11#update=introduce parse error
12#file=main.zig12#file=main.zig
13pub fn main() !void {13pub fn main() !void {
14#expect_error=ignored14#expect_error=main.zig:2:1: error: expected statement, found 'EOF'
1515
16#update=fix parse error16#update=fix parse error
17#file=main.zig17#file=main.zig
test/incremental/unreferenced_error+1-1
...@@ -18,7 +18,7 @@ pub fn main() !void {...@@ -18,7 +18,7 @@ pub fn main() !void {
18 try std.io.getStdOut().writeAll(a);18 try std.io.getStdOut().writeAll(a);
19}19}
20const a = @compileError("bad a");20const a = @compileError("bad a");
21#expect_error=ignored21#expect_error=main.zig:5:11: error: bad a
2222
23#update=remove error reference23#update=remove error reference
24#file=main.zig24#file=main.zig
tools/incr-check.zig+118-19
...@@ -340,19 +340,63 @@ const Eval = struct {...@@ -340,19 +340,63 @@ const Eval = struct {
340 }340 }
341341
342 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {342 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
343 switch (update.outcome) {343 const expected_errors = switch (update.outcome) {
344 .unknown => return,344 .unknown => return,
345 .compile_errors => |expected_errors| {345 .compile_errors => |expected_errors| expected_errors,
346 for (expected_errors) |expected_error| {
347 _ = expected_error;
348 @panic("TODO check if the expected error matches the compile errors");
349 }
350 },
351 .stdout, .exit_code => {346 .stdout, .exit_code => {
352 const color: std.zig.Color = .auto;347 const color: std.zig.Color = .auto;
353 error_bundle.renderToStdErr(color.renderOptions());348 error_bundle.renderToStdErr(color.renderOptions());
354 eval.fatal("update '{s}': unexpected compile errors", .{update.name});349 eval.fatal("update '{s}': unexpected compile errors", .{update.name});
355 },350 },
351 };
352
353 var expected_idx: usize = 0;
354
355 for (error_bundle.getMessages()) |err_idx| {
356 if (expected_idx == expected_errors.len) {
357 const color: std.zig.Color = .auto;
358 error_bundle.renderToStdErr(color.renderOptions());
359 eval.fatal("update '{s}': more errors than expected", .{update.name});
360 }
361 eval.checkOneError(update, error_bundle, expected_errors[expected_idx], false, err_idx);
362 expected_idx += 1;
363
364 for (error_bundle.getNotes(err_idx)) |note_idx| {
365 if (expected_idx == expected_errors.len) {
366 const color: std.zig.Color = .auto;
367 error_bundle.renderToStdErr(color.renderOptions());
368 eval.fatal("update '{s}': more error notes than expected", .{update.name});
369 }
370 eval.checkOneError(update, error_bundle, expected_errors[expected_idx], true, note_idx);
371 expected_idx += 1;
372 }
373 }
374 }
375
376 fn checkOneError(
377 eval: *Eval,
378 update: Case.Update,
379 eb: std.zig.ErrorBundle,
380 expected: Case.ExpectedError,
381 is_note: bool,
382 err_idx: std.zig.ErrorBundle.MessageIndex,
383 ) void {
384 const err = eb.getErrorMessage(err_idx);
385 if (err.src_loc == .none) @panic("TODO error message with no source location");
386 if (err.count != 1) @panic("TODO error message with count>1");
387 const msg = eb.nullTerminatedString(err.msg);
388 const src = eb.getSourceLocation(err.src_loc);
389 const filename = eb.nullTerminatedString(src.src_path);
390
391 if (expected.is_note != is_note or
392 !std.mem.eql(u8, expected.filename, filename) or
393 expected.line != src.line + 1 or
394 expected.column != src.column + 1 or
395 !std.mem.eql(u8, expected.msg, msg))
396 {
397 const color: std.zig.Color = .auto;
398 eb.renderToStdErr(color.renderOptions());
399 eval.fatal("update '{s}': compile error did not match expected error", .{update.name});
356 }400 }
357 }401 }
358402
...@@ -595,11 +639,11 @@ const Case = struct {...@@ -595,11 +639,11 @@ const Case = struct {
595 };639 };
596640
597 const ExpectedError = struct {641 const ExpectedError = struct {
598 file_name: ?[]const u8 = null,642 is_note: bool,
599 line: ?u32 = null,643 filename: []const u8,
600 column: ?u32 = null,644 line: u32,
601 msg_exact: ?[]const u8 = null,645 column: u32,
602 msg_substring: ?[]const u8 = null,646 msg: []const u8,
603 };647 };
604648
605 fn parse(arena: Allocator, bytes: []const u8) !Case {649 fn parse(arena: Allocator, bytes: []const u8) !Case {
...@@ -665,13 +709,11 @@ const Case = struct {...@@ -665,13 +709,11 @@ const Case = struct {
665 var src: std.ArrayListUnmanaged(u8) = .empty;709 var src: std.ArrayListUnmanaged(u8) = .empty;
666710
667 while (true) {711 while (true) {
668 const old = it;712 const next_line_raw = it.peek() orelse fatal("line {d}: unexpected EOF", .{line_n});
669 const next_line_raw = it.next() orelse fatal("line {d}: unexpected EOF", .{line_n});
670 const next_line = std.mem.trimRight(u8, next_line_raw, "\r");713 const next_line = std.mem.trimRight(u8, next_line_raw, "\r");
671 if (std.mem.startsWith(u8, next_line, "#")) {714 if (std.mem.startsWith(u8, next_line, "#")) break;
672 it = old;715
673 break;716 _ = it.next();
674 }
675 line_n += 1;717 line_n += 1;
676718
677 try src.ensureUnusedCapacity(arena, next_line.len + 1);719 try src.ensureUnusedCapacity(arena, next_line.len + 1);
...@@ -699,7 +741,24 @@ const Case = struct {...@@ -699,7 +741,24 @@ const Case = struct {
699 if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n});741 if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n});
700 const last_update = &updates.items[updates.items.len - 1];742 const last_update = &updates.items[updates.items.len - 1];
701 if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n});743 if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n});
702 last_update.outcome = .{ .compile_errors = &.{} };744
745 var errors: std.ArrayListUnmanaged(ExpectedError) = .empty;
746 try errors.append(arena, parseExpectedError(val, line_n));
747 while (true) {
748 const next_line = it.peek() orelse break;
749 if (!std.mem.startsWith(u8, next_line, "#")) break;
750 var new_line_it = std.mem.splitScalar(u8, next_line, '=');
751 const new_key = new_line_it.first()[1..];
752 const new_val = std.mem.trimRight(u8, new_line_it.rest(), "\r");
753 if (new_val.len == 0) break;
754 if (!std.mem.eql(u8, new_key, "expect_error")) break;
755
756 _ = it.next();
757 line_n += 1;
758 try errors.append(arena, parseExpectedError(new_val, line_n));
759 }
760
761 last_update.outcome = .{ .compile_errors = errors.items };
703 } else {762 } else {
704 fatal("line {d}: unrecognized key '{s}'", .{ line_n, key });763 fatal("line {d}: unrecognized key '{s}'", .{ line_n, key });
705 }764 }
...@@ -749,3 +808,43 @@ fn waitChild(child: *std.process.Child, eval: *Eval) void {...@@ -749,3 +808,43 @@ fn waitChild(child: *std.process.Child, eval: *Eval) void {
749 .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}),808 .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}),
750 }809 }
751}810}
811
812fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError {
813 // #expect_error=foo.zig:1:2: error: the error message
814 // #expect_error=foo.zig:1:2: note: and a note
815
816 const fatal = std.process.fatal;
817
818 var it = std.mem.splitScalar(u8, str, ':');
819 const filename = it.first();
820 const line_str = it.next() orelse fatal("line {d}: incomplete error specification", .{l});
821 const column_str = it.next() orelse fatal("line {d}: incomplete error specification", .{l});
822 const error_or_note_str = std.mem.trim(
823 u8,
824 it.next() orelse fatal("line {d}: incomplete error specification", .{l}),
825 " ",
826 );
827 const message = std.mem.trim(u8, it.rest(), " ");
828 if (filename.len == 0) fatal("line {d}: empty filename", .{l});
829 if (message.len == 0) fatal("line {d}: empty error message", .{l});
830 const is_note = if (std.mem.eql(u8, error_or_note_str, "error"))
831 false
832 else if (std.mem.eql(u8, error_or_note_str, "note"))
833 true
834 else
835 fatal("line {d}: expeted 'error' or 'note', found '{s}'", .{ l, error_or_note_str });
836
837 const line = std.fmt.parseInt(u32, line_str, 10) catch
838 fatal("line {d}: invalid line number '{s}'", .{ l, line_str });
839
840 const column = std.fmt.parseInt(u32, column_str, 10) catch
841 fatal("line {d}: invalid column number '{s}'", .{ l, column_str });
842
843 return .{
844 .is_note = is_note,
845 .filename = filename,
846 .line = line,
847 .column = column,
848 .msg = message,
849 };
850}