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 {...@@ -1346,9 +1346,9 @@ pub fn update(self: *Compilation) !void {
1346 module.generation += 1;1346 module.generation += 1;
13471347
1348 // TODO Detect which source files changed.1348 // TODO Detect which source files changed.
1349 // Until then we simulate a full cache miss. Source files could have been loaded for any reason;1349 // Until then we simulate a full cache miss. Source files could have been loaded
1350 // to force a refresh we unload now.1350 // for any reason; to force a refresh we unload now.
1351 module.root_scope.unload(module.gpa);1351 module.unloadFile(module.root_scope);
1352 module.failed_root_src_file = null;1352 module.failed_root_src_file = null;
1353 module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) {1353 module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) {
1354 error.AnalysisFail => {1354 error.AnalysisFail => {
...@@ -1362,7 +1362,7 @@ pub fn update(self: *Compilation) !void {...@@ -1362,7 +1362,7 @@ pub fn update(self: *Compilation) !void {
13621362
1363 // TODO only analyze imports if they are still referenced1363 // TODO only analyze imports if they are still referenced
1364 for (module.import_table.items()) |entry| {1364 for (module.import_table.items()) |entry| {
1365 entry.value.unload(module.gpa);1365 module.unloadFile(entry.value);
1366 module.analyzeContainer(&entry.value.root_container) catch |err| switch (err) {1366 module.analyzeContainer(&entry.value.root_container) catch |err| switch (err) {
1367 error.AnalysisFail => {1367 error.AnalysisFail => {
1368 assert(self.totalErrorCount() != 0);1368 assert(self.totalErrorCount() != 0);
...@@ -1432,11 +1432,25 @@ pub fn totalErrorCount(self: *Compilation) usize {...@@ -1432,11 +1432,25 @@ pub fn totalErrorCount(self: *Compilation) usize {
1432 var total: usize = self.failed_c_objects.items().len;1432 var total: usize = self.failed_c_objects.items().len;
14331433
1434 if (self.bin_file.options.module) |module| {1434 if (self.bin_file.options.module) |module| {
1435 total += module.failed_decls.count() +1435 total += module.failed_exports.items().len +
1436 module.emit_h_failed_decls.count() +
1437 module.failed_exports.items().len +
1438 module.failed_files.items().len +1436 module.failed_files.items().len +
1439 @boolToInt(module.failed_root_src_file != null);1437 @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 }
1440 }1454 }
14411455
1442 // The "no entry point found" error only counts if there are no other errors.1456 // The "no entry point found" error only counts if there are no other errors.
...@@ -1483,9 +1497,19 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {...@@ -1483,9 +1497,19 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
1483 try AllErrors.add(module, &arena, &errors, entry.value.*);1497 try AllErrors.add(module, &arena, &errors, entry.value.*);
1484 }1498 }
1485 for (module.failed_decls.items()) |entry| {1499 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 }
1486 try AllErrors.add(module, &arena, &errors, entry.value.*);1505 try AllErrors.add(module, &arena, &errors, entry.value.*);
1487 }1506 }
1488 for (module.emit_h_failed_decls.items()) |entry| {1507 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 }
1489 try AllErrors.add(module, &arena, &errors, entry.value.*);1513 try AllErrors.add(module, &arena, &errors, entry.value.*);
1490 }1514 }
1491 for (module.failed_exports.items()) |entry| {1515 for (module.failed_exports.items()) |entry| {
src/Module.zig+14-5
...@@ -65,8 +65,8 @@ emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{},...@@ -65,8 +65,8 @@ emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{},
65/// Keep track of one `@compileLog` callsite per owner Decl.65/// Keep track of one `@compileLog` callsite per owner Decl.
66compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, SrcLoc) = .{},66compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, SrcLoc) = .{},
67/// Using a map here for consistency with the other fields here.67/// 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.68/// The ErrorMsg memory is owned by the `Scope.File`, using Module's general purpose allocator.
69failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *ErrorMsg) = .{},69failed_files: std.AutoArrayHashMapUnmanaged(*Scope.File, *ErrorMsg) = .{},
70/// Using a map here for consistency with the other fields here.70/// Using a map here for consistency with the other fields here.
71/// The ErrorMsg memory is owned by the `Export`, using Module's general purpose allocator.71/// The ErrorMsg memory is owned by the `Export`, using Module's general purpose allocator.
72failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{},72failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{},
...@@ -732,10 +732,12 @@ pub const Scope = struct {...@@ -732,10 +732,12 @@ pub const Scope = struct {
732732
733 pub fn unload(file: *File, gpa: *Allocator) void {733 pub fn unload(file: *File, gpa: *Allocator) void {
734 switch (file.status) {734 switch (file.status) {
735 .never_loaded,
736 .unloaded_parse_failure,735 .unloaded_parse_failure,
736 .never_loaded,
737 .unloaded_success,737 .unloaded_success,
738 => {},738 => {
739 file.status = .unloaded_success;
740 },
739741
740 .loaded_success => {742 .loaded_success => {
741 file.tree.deinit(gpa);743 file.tree.deinit(gpa);
...@@ -3241,7 +3243,7 @@ pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree {...@@ -3241,7 +3243,7 @@ pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree {
3241 .msg = msg.toOwnedSlice(),3243 .msg = msg.toOwnedSlice(),
3242 };3244 };
32433245
3244 mod.failed_files.putAssumeCapacityNoClobber(&root_scope.base, err_msg);3246 mod.failed_files.putAssumeCapacityNoClobber(root_scope, err_msg);
3245 root_scope.status = .unloaded_parse_failure;3247 root_scope.status = .unloaded_parse_failure;
3246 return error.AnalysisFail;3248 return error.AnalysisFail;
3247 }3249 }
...@@ -4691,3 +4693,10 @@ pub fn parseStrLit(...@@ -4691,3 +4693,10 @@ pub fn parseStrLit(
4691 },4693 },
4692 }4694 }
4693}4695}
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 {...@@ -600,6 +600,7 @@ pub fn addCases(ctx: *TestContext) !void {
600 , "");600 , "");
601601
602 // Specifying alignment is a parse error.602 // Specifying alignment is a parse error.
603 // This also tests going from a successful build to a parse error.
603 case.addError(604 case.addError(
604 \\const E1 = enum {605 \\const E1 = enum {
605 \\ a,606 \\ a,
...@@ -612,6 +613,24 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -612,6 +613,24 @@ pub fn addCases(ctx: *TestContext) !void {
612 , &.{613 , &.{
613 ":3:7: error: expected ',', found 'align'",614 ":3:7: error: expected ',', found 'align'",
614 });615 });
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 });
615 }634 }
616635
617 ctx.c("empty start function", linux_x64,636 ctx.c("empty start function", linux_x64,