| author | |
| committer | |
| log | 12087d4cbaab39acadc29716e92765c92b92e28c |
| tree | bb5447848ff6bdd3477aac5ab193363fd666abcf |
| parent | 4996c2b6a94b042d86b50eb61c9d8d98e63415af |
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; |
| 1347 | 1347 | ||
| 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 { |
| 1362 | 1362 | ||
| 1363 | // TODO only analyze imports if they are still referenced | 1363 | // 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; |
| 1433 | 1433 | ||
| 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 | } |
| 1441 | 1455 | ||
| 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. |
| 66 | compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, SrcLoc) = .{}, | 66 | compile_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. |
| 69 | failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *ErrorMsg) = .{}, | 69 | failed_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. |
| 72 | failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{}, | 72 | failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{}, |
| ... | @@ -732,10 +732,12 @@ pub const Scope = struct { | ... | @@ -732,10 +732,12 @@ pub const Scope = struct { |
| 732 | 732 | ||
| 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 | }, | ||
| 739 | 741 | ||
| 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 | }; |
| 3243 | 3245 | ||
| 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 | |||
| 4697 | pub 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 | , ""); |
| 601 | 601 | ||
| 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 | } |
| 616 | 635 | ||
| 617 | ctx.c("empty start function", linux_x64, | 636 | ctx.c("empty start function", linux_x64, |