authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-07 20:36:01-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-07 20:36:01-07:00
log12087d4cbaab39acadc29716e92765c92b92e28c
treebb5447848ff6bdd3477aac5ab193363fd666abcf
parent4996c2b6a94b042d86b50eb61c9d8d98e63415af

stage2: fix incremental compilation handling of parse errors

Before, incremental compilation would crash when trying to emit compile errors for the update after introducing a parse error. Parse errors are handled by not invalidating any existing semantic analysis. However, only the parse error must be reported, with all the other errors suppressed. Once the parse error is fixed, the new file can be treated as an update to the previously-succeeded update.

3 files changed, 64 insertions(+), 12 deletions(-)

src/Compilation.zig+31-7
......@@ -1346,9 +1346,9 @@ pub fn update(self: *Compilation) !void {
13461346 module.generation += 1;
13471347
13481348 // TODO Detect which source files changed.
1349 // Until then we simulate a full cache miss. Source files could have been loaded for any reason;
1350 // to force a refresh we unload now.
1351 module.root_scope.unload(module.gpa);
1349 // Until then we simulate a full cache miss. Source files could have been loaded
1350 // for any reason; to force a refresh we unload now.
1351 module.unloadFile(module.root_scope);
13521352 module.failed_root_src_file = null;
13531353 module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) {
13541354 error.AnalysisFail => {
......@@ -1362,7 +1362,7 @@ pub fn update(self: *Compilation) !void {
13621362
13631363 // TODO only analyze imports if they are still referenced
13641364 for (module.import_table.items()) |entry| {
1365 entry.value.unload(module.gpa);
1365 module.unloadFile(entry.value);
13661366 module.analyzeContainer(&entry.value.root_container) catch |err| switch (err) {
13671367 error.AnalysisFail => {
13681368 assert(self.totalErrorCount() != 0);
......@@ -1432,11 +1432,25 @@ pub fn totalErrorCount(self: *Compilation) usize {
14321432 var total: usize = self.failed_c_objects.items().len;
14331433
14341434 if (self.bin_file.options.module) |module| {
1435 total += module.failed_decls.count() +
1436 module.emit_h_failed_decls.count() +
1437 module.failed_exports.items().len +
1435 total += module.failed_exports.items().len +
14381436 module.failed_files.items().len +
14391437 @boolToInt(module.failed_root_src_file != null);
1438 // Skip errors for Decls within files that failed parsing.
1439 // When a parse error is introduced, we keep all the semantic analysis for
1440 // the previous parse success, including compile errors, but we cannot
1441 // emit them until the file succeeds parsing.
1442 for (module.failed_decls.items()) |entry| {
1443 if (entry.key.container.file_scope.status == .unloaded_parse_failure) {
1444 continue;
1445 }
1446 total += 1;
1447 }
1448 for (module.emit_h_failed_decls.items()) |entry| {
1449 if (entry.key.container.file_scope.status == .unloaded_parse_failure) {
1450 continue;
1451 }
1452 total += 1;
1453 }
14401454 }
14411455
14421456 // The "no entry point found" error only counts if there are no other errors.
......@@ -1483,9 +1497,19 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
14831497 try AllErrors.add(module, &arena, &errors, entry.value.*);
14841498 }
14851499 for (module.failed_decls.items()) |entry| {
1500 if (entry.key.container.file_scope.status == .unloaded_parse_failure) {
1501 // Skip errors for Decls within files that had a parse failure.
1502 // We'll try again once parsing succeeds.
1503 continue;
1504 }
14861505 try AllErrors.add(module, &arena, &errors, entry.value.*);
14871506 }
14881507 for (module.emit_h_failed_decls.items()) |entry| {
1508 if (entry.key.container.file_scope.status == .unloaded_parse_failure) {
1509 // Skip errors for Decls within files that had a parse failure.
1510 // We'll try again once parsing succeeds.
1511 continue;
1512 }
14891513 try AllErrors.add(module, &arena, &errors, entry.value.*);
14901514 }
14911515 for (module.failed_exports.items()) |entry| {
src/Module.zig+14-5
......@@ -65,8 +65,8 @@ emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{},
6565/// Keep track of one `@compileLog` callsite per owner Decl.
6666compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, SrcLoc) = .{},
6767/// Using a map here for consistency with the other fields here.
68/// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator.
69failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *ErrorMsg) = .{},
68/// The ErrorMsg memory is owned by the `Scope.File`, using Module's general purpose allocator.
69failed_files: std.AutoArrayHashMapUnmanaged(*Scope.File, *ErrorMsg) = .{},
7070/// Using a map here for consistency with the other fields here.
7171/// The ErrorMsg memory is owned by the `Export`, using Module's general purpose allocator.
7272failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{},
......@@ -732,10 +732,12 @@ pub const Scope = struct {
732732
733733 pub fn unload(file: *File, gpa: *Allocator) void {
734734 switch (file.status) {
735 .never_loaded,
736735 .unloaded_parse_failure,
736 .never_loaded,
737737 .unloaded_success,
738 => {},
738 => {
739 file.status = .unloaded_success;
740 },
739741
740742 .loaded_success => {
741743 file.tree.deinit(gpa);
......@@ -3241,7 +3243,7 @@ pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree {
32413243 .msg = msg.toOwnedSlice(),
32423244 };
32433245
3244 mod.failed_files.putAssumeCapacityNoClobber(&root_scope.base, err_msg);
3246 mod.failed_files.putAssumeCapacityNoClobber(root_scope, err_msg);
32453247 root_scope.status = .unloaded_parse_failure;
32463248 return error.AnalysisFail;
32473249 }
......@@ -4691,3 +4693,10 @@ pub fn parseStrLit(
46914693 },
46924694 }
46934695}
4696
4697pub fn unloadFile(mod: *Module, file_scope: *Scope.File) void {
4698 if (file_scope.status == .unloaded_parse_failure) {
4699 mod.failed_files.swapRemove(file_scope).?.value.destroy(mod.gpa);
4700 }
4701 file_scope.unload(mod.gpa);
4702}
test/stage2/cbe.zig+19
......@@ -600,6 +600,7 @@ pub fn addCases(ctx: *TestContext) !void {
600600 , "");
601601
602602 // Specifying alignment is a parse error.
603 // This also tests going from a successful build to a parse error.
603604 case.addError(
604605 \\const E1 = enum {
605606 \\ a,
......@@ -612,6 +613,24 @@ pub fn addCases(ctx: *TestContext) !void {
612613 , &.{
613614 ":3:7: error: expected ',', found 'align'",
614615 });
616
617 // Redundant non-exhaustive enum mark.
618 // This also tests going from a parse error to an AstGen error.
619 case.addError(
620 \\const E1 = enum {
621 \\ a,
622 \\ _,
623 \\ b,
624 \\ c,
625 \\ _,
626 \\};
627 \\export fn foo() void {
628 \\ const x = E1.a;
629 \\}
630 , &.{
631 ":6:5: error: redundant non-exhaustive enum mark",
632 ":3:5: note: other mark here",
633 });
615634 }
616635
617636 ctx.c("empty start function", linux_x64,