authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-07 19:38:00-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-07 19:54:28-07:00
log4996c2b6a94b042d86b50eb61c9d8d98e63415af
tree3857fef9a889a57d20925005579e7ef6784ea2e8
parent8f28e26e7a4f770f8d8e700386e2ade111948891

stage2: fix incremental compilation Decl deletion logic

* `analyzeContainer` now has an `outdated_decls` set as well as `deleted_decls`. Instead of queuing up outdated Decls for re-analysis right away, they are added to this new set. When processing the `deleted_decls` set, we remove deleted Decls from the `outdated_decls` set, to avoid deleted Decl pointers from being in the work_queue. Only after processing the deleted decls do we add analyze_decl work items to the queue. * Module.deletion_set is now an `AutoArrayHashMap` rather than `ArrayList`. `declareDeclDependency` will now remove a Decl from it as appropriate. When processing the `deletion_set` in `Compilation.performAllTheWork`, it now assumes all Decl in the set are to be deleted. * Fix crash when handling parse errors. Currently we unload the `ast.Tree` if any parse errors occur. Previously the code emitted a LazySrcLoc pointing to a token index, but then when we try to resolve the token index to a byte offset to create a compile error message, the ast.Tree` would be unloaded. Now we use `LazySrcLoc.byte_abs` instead of `token_abs` so the error message can be created even with the `ast.Tree` unloaded. Together, these changes solve a crash that happened with incremental compilation when Decls were added and removed in some combinations.

5 files changed, 145 insertions(+), 70 deletions(-)

src/AstGen.zig+3-3
...@@ -1899,9 +1899,9 @@ fn containerDecl(...@@ -1899,9 +1899,9 @@ fn containerDecl(
1899 if (member.ast.type_expr != 0) {1899 if (member.ast.type_expr != 0) {
1900 return mod.failNode(scope, member.ast.type_expr, "enum fields do not have types", .{});1900 return mod.failNode(scope, member.ast.type_expr, "enum fields do not have types", .{});
1901 }1901 }
1902 if (member.ast.align_expr != 0) {1902 // Alignment expressions in enums are caught by the parser.
1903 return mod.failNode(scope, member.ast.align_expr, "enum fields do not have alignments", .{});1903 assert(member.ast.align_expr == 0);
1904 }1904
1905 const name_token = member.ast.name_token;1905 const name_token = member.ast.name_token;
1906 if (mem.eql(u8, tree.tokenSlice(name_token), "_")) {1906 if (mem.eql(u8, tree.tokenSlice(name_token), "_")) {
1907 if (nonexhaustive_node != 0) {1907 if (nonexhaustive_node != 0) {
src/Compilation.zig+10-7
...@@ -1377,14 +1377,17 @@ pub fn update(self: *Compilation) !void {...@@ -1377,14 +1377,17 @@ pub fn update(self: *Compilation) !void {
13771377
1378 if (!use_stage1) {1378 if (!use_stage1) {
1379 if (self.bin_file.options.module) |module| {1379 if (self.bin_file.options.module) |module| {
1380 // Process the deletion set.1380 // Process the deletion set. We use a while loop here because the
1381 while (module.deletion_set.popOrNull()) |decl| {1381 // deletion set may grow as we call `deleteDecl` within this loop,
1382 if (decl.dependants.items().len != 0) {1382 // and more unreferenced Decls are revealed.
1383 decl.deletion_flag = false;1383 var entry_i: usize = 0;
1384 continue;1384 while (entry_i < module.deletion_set.entries.items.len) : (entry_i += 1) {
1385 }1385 const decl = module.deletion_set.entries.items[entry_i].key;
1386 try module.deleteDecl(decl);1386 assert(decl.deletion_flag);
1387 assert(decl.dependants.items().len == 0);
1388 try module.deleteDecl(decl, null);
1387 }1389 }
1390 module.deletion_set.shrinkRetainingCapacity(0);
1388 }1391 }
1389 }1392 }
13901393
src/Module.zig+79-18
...@@ -75,7 +75,7 @@ next_anon_name_index: usize = 0,...@@ -75,7 +75,7 @@ next_anon_name_index: usize = 0,
7575
76/// Candidates for deletion. After a semantic analysis update completes, this list76/// Candidates for deletion. After a semantic analysis update completes, this list
77/// contains Decls that need to be deleted if they end up having no references to them.77/// contains Decls that need to be deleted if they end up having no references to them.
78deletion_set: ArrayListUnmanaged(*Decl) = .{},78deletion_set: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
7979
80/// Error tags and their values, tag names are duped with mod.gpa.80/// Error tags and their values, tag names are duped with mod.gpa.
81/// Corresponds with `error_name_list`.81/// Corresponds with `error_name_list`.
...@@ -192,7 +192,7 @@ pub const Decl = struct {...@@ -192,7 +192,7 @@ pub const Decl = struct {
192 /// to require re-analysis.192 /// to require re-analysis.
193 outdated,193 outdated,
194 },194 },
195 /// This flag is set when this Decl is added to a check_for_deletion set, and cleared195 /// This flag is set when this Decl is added to `Module.deletion_set`, and cleared
196 /// when removed.196 /// when removed.
197 deletion_flag: bool,197 deletion_flag: bool,
198 /// Whether the corresponding AST decl has a `pub` keyword.198 /// Whether the corresponding AST decl has a `pub` keyword.
...@@ -2393,7 +2393,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void {...@@ -2393,7 +2393,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void {
2393 // We don't perform a deletion here, because this Decl or another one2393 // We don't perform a deletion here, because this Decl or another one
2394 // may end up referencing it before the update is complete.2394 // may end up referencing it before the update is complete.
2395 dep.deletion_flag = true;2395 dep.deletion_flag = true;
2396 try mod.deletion_set.append(mod.gpa, dep);2396 try mod.deletion_set.put(mod.gpa, dep, {});
2397 }2397 }
2398 }2398 }
2399 decl.dependencies.clearRetainingCapacity();2399 decl.dependencies.clearRetainingCapacity();
...@@ -3197,6 +3197,11 @@ pub fn declareDeclDependency(mod: *Module, depender: *Decl, dependee: *Decl) !u3...@@ -3197,6 +3197,11 @@ pub fn declareDeclDependency(mod: *Module, depender: *Decl, dependee: *Decl) !u3
3197 try depender.dependencies.ensureCapacity(mod.gpa, depender.dependencies.count() + 1);3197 try depender.dependencies.ensureCapacity(mod.gpa, depender.dependencies.count() + 1);
3198 try dependee.dependants.ensureCapacity(mod.gpa, dependee.dependants.count() + 1);3198 try dependee.dependants.ensureCapacity(mod.gpa, dependee.dependants.count() + 1);
31993199
3200 if (dependee.deletion_flag) {
3201 dependee.deletion_flag = false;
3202 mod.deletion_set.removeAssertDiscard(dependee);
3203 }
3204
3200 dependee.dependants.putAssumeCapacity(depender, {});3205 dependee.dependants.putAssumeCapacity(depender, {});
3201 const gop = depender.dependencies.getOrPutAssumeCapacity(dependee);3206 const gop = depender.dependencies.getOrPutAssumeCapacity(dependee);
3202 return @intCast(u32, gop.index);3207 return @intCast(u32, gop.index);
...@@ -3224,12 +3229,14 @@ pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree {...@@ -3224,12 +3229,14 @@ pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree {
3224 var msg = std.ArrayList(u8).init(mod.gpa);3229 var msg = std.ArrayList(u8).init(mod.gpa);
3225 defer msg.deinit();3230 defer msg.deinit();
32263231
3232 const token_starts = tree.tokens.items(.start);
3233
3227 try tree.renderError(parse_err, msg.writer());3234 try tree.renderError(parse_err, msg.writer());
3228 const err_msg = try mod.gpa.create(ErrorMsg);3235 const err_msg = try mod.gpa.create(ErrorMsg);
3229 err_msg.* = .{3236 err_msg.* = .{
3230 .src_loc = .{3237 .src_loc = .{
3231 .container = .{ .file_scope = root_scope },3238 .container = .{ .file_scope = root_scope },
3232 .lazy = .{ .token_abs = parse_err.token },3239 .lazy = .{ .byte_abs = token_starts[parse_err.token] },
3233 },3240 },
3234 .msg = msg.toOwnedSlice(),3241 .msg = msg.toOwnedSlice(),
3235 };3242 };
...@@ -3274,6 +3281,14 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3274,6 +3281,14 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3274 deleted_decls.putAssumeCapacityNoClobber(entry.key, {});3281 deleted_decls.putAssumeCapacityNoClobber(entry.key, {});
3275 }3282 }
32763283
3284 // Keep track of decls that are invalidated from the update. Ultimately,
3285 // the goal is to queue up `analyze_decl` tasks in the work queue for
3286 // the outdated decls, but we cannot queue up the tasks until after
3287 // we find out which ones have been deleted, otherwise there would be
3288 // deleted Decl pointers in the work queue.
3289 var outdated_decls = std.AutoArrayHashMap(*Decl, void).init(mod.gpa);
3290 defer outdated_decls.deinit();
3291
3277 for (decls) |decl_node, decl_i| switch (node_tags[decl_node]) {3292 for (decls) |decl_node, decl_i| switch (node_tags[decl_node]) {
3278 .fn_decl => {3293 .fn_decl => {
3279 const fn_proto = node_datas[decl_node].lhs;3294 const fn_proto = node_datas[decl_node].lhs;
...@@ -3284,6 +3299,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3284,6 +3299,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3284 try mod.semaContainerFn(3299 try mod.semaContainerFn(
3285 container_scope,3300 container_scope,
3286 &deleted_decls,3301 &deleted_decls,
3302 &outdated_decls,
3287 decl_node,3303 decl_node,
3288 decl_i,3304 decl_i,
3289 tree.*,3305 tree.*,
...@@ -3294,6 +3310,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3294,6 +3310,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3294 .fn_proto_multi => try mod.semaContainerFn(3310 .fn_proto_multi => try mod.semaContainerFn(
3295 container_scope,3311 container_scope,
3296 &deleted_decls,3312 &deleted_decls,
3313 &outdated_decls,
3297 decl_node,3314 decl_node,
3298 decl_i,3315 decl_i,
3299 tree.*,3316 tree.*,
...@@ -3305,6 +3322,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3305,6 +3322,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3305 try mod.semaContainerFn(3322 try mod.semaContainerFn(
3306 container_scope,3323 container_scope,
3307 &deleted_decls,3324 &deleted_decls,
3325 &outdated_decls,
3308 decl_node,3326 decl_node,
3309 decl_i,3327 decl_i,
3310 tree.*,3328 tree.*,
...@@ -3315,6 +3333,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3315,6 +3333,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3315 .fn_proto => try mod.semaContainerFn(3333 .fn_proto => try mod.semaContainerFn(
3316 container_scope,3334 container_scope,
3317 &deleted_decls,3335 &deleted_decls,
3336 &outdated_decls,
3318 decl_node,3337 decl_node,
3319 decl_i,3338 decl_i,
3320 tree.*,3339 tree.*,
...@@ -3329,6 +3348,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3329,6 +3348,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3329 try mod.semaContainerFn(3348 try mod.semaContainerFn(
3330 container_scope,3349 container_scope,
3331 &deleted_decls,3350 &deleted_decls,
3351 &outdated_decls,
3332 decl_node,3352 decl_node,
3333 decl_i,3353 decl_i,
3334 tree.*,3354 tree.*,
...@@ -3339,6 +3359,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3339,6 +3359,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3339 .fn_proto_multi => try mod.semaContainerFn(3359 .fn_proto_multi => try mod.semaContainerFn(
3340 container_scope,3360 container_scope,
3341 &deleted_decls,3361 &deleted_decls,
3362 &outdated_decls,
3342 decl_node,3363 decl_node,
3343 decl_i,3364 decl_i,
3344 tree.*,3365 tree.*,
...@@ -3350,6 +3371,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3350,6 +3371,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3350 try mod.semaContainerFn(3371 try mod.semaContainerFn(
3351 container_scope,3372 container_scope,
3352 &deleted_decls,3373 &deleted_decls,
3374 &outdated_decls,
3353 decl_node,3375 decl_node,
3354 decl_i,3376 decl_i,
3355 tree.*,3377 tree.*,
...@@ -3360,6 +3382,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3360,6 +3382,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3360 .fn_proto => try mod.semaContainerFn(3382 .fn_proto => try mod.semaContainerFn(
3361 container_scope,3383 container_scope,
3362 &deleted_decls,3384 &deleted_decls,
3385 &outdated_decls,
3363 decl_node,3386 decl_node,
3364 decl_i,3387 decl_i,
3365 tree.*,3388 tree.*,
...@@ -3370,6 +3393,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3370,6 +3393,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3370 .global_var_decl => try mod.semaContainerVar(3393 .global_var_decl => try mod.semaContainerVar(
3371 container_scope,3394 container_scope,
3372 &deleted_decls,3395 &deleted_decls,
3396 &outdated_decls,
3373 decl_node,3397 decl_node,
3374 decl_i,3398 decl_i,
3375 tree.*,3399 tree.*,
...@@ -3378,6 +3402,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3378,6 +3402,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3378 .local_var_decl => try mod.semaContainerVar(3402 .local_var_decl => try mod.semaContainerVar(
3379 container_scope,3403 container_scope,
3380 &deleted_decls,3404 &deleted_decls,
3405 &outdated_decls,
3381 decl_node,3406 decl_node,
3382 decl_i,3407 decl_i,
3383 tree.*,3408 tree.*,
...@@ -3386,6 +3411,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3386,6 +3411,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3386 .simple_var_decl => try mod.semaContainerVar(3411 .simple_var_decl => try mod.semaContainerVar(
3387 container_scope,3412 container_scope,
3388 &deleted_decls,3413 &deleted_decls,
3414 &outdated_decls,
3389 decl_node,3415 decl_node,
3390 decl_i,3416 decl_i,
3391 tree.*,3417 tree.*,
...@@ -3394,6 +3420,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3394,6 +3420,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3394 .aligned_var_decl => try mod.semaContainerVar(3420 .aligned_var_decl => try mod.semaContainerVar(
3395 container_scope,3421 container_scope,
3396 &deleted_decls,3422 &deleted_decls,
3423 &outdated_decls,
3397 decl_node,3424 decl_node,
3398 decl_i,3425 decl_i,
3399 tree.*,3426 tree.*,
...@@ -3446,11 +3473,27 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {...@@ -3446,11 +3473,27 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
3446 },3473 },
3447 else => unreachable,3474 else => unreachable,
3448 };3475 };
3449 // Handle explicitly deleted decls from the source code. Not to be confused3476 // Handle explicitly deleted decls from the source code. This is one of two
3450 // with when we delete decls because they are no longer referenced.3477 // places that Decl deletions happen. The other is in `Compilation`, after
3478 // `performAllTheWork`, where we iterate over `Module.deletion_set` and
3479 // delete Decls which are no longer referenced.
3480 // If a Decl is explicitly deleted from source, and also no longer referenced,
3481 // it may be both in this `deleted_decls` set, as well as in the
3482 // `Module.deletion_set`. To avoid deleting it twice, we remove it from the
3483 // deletion set at this time.
3451 for (deleted_decls.items()) |entry| {3484 for (deleted_decls.items()) |entry| {
3452 log.debug("noticed '{s}' deleted from source", .{entry.key.name});3485 const decl = entry.key;
3453 try mod.deleteDecl(entry.key);3486 log.debug("'{s}' deleted from source", .{decl.name});
3487 if (decl.deletion_flag) {
3488 log.debug("'{s}' redundantly in deletion set; removing", .{decl.name});
3489 mod.deletion_set.removeAssertDiscard(decl);
3490 }
3491 try mod.deleteDecl(decl, &outdated_decls);
3492 }
3493 // Finally we can queue up re-analysis tasks after we have processed
3494 // the deleted decls.
3495 for (outdated_decls.items()) |entry| {
3496 try mod.markOutdatedDecl(entry.key);
3454 }3497 }
3455}3498}
34563499
...@@ -3458,6 +3501,7 @@ fn semaContainerFn(...@@ -3458,6 +3501,7 @@ fn semaContainerFn(
3458 mod: *Module,3501 mod: *Module,
3459 container_scope: *Scope.Container,3502 container_scope: *Scope.Container,
3460 deleted_decls: *std.AutoArrayHashMap(*Decl, void),3503 deleted_decls: *std.AutoArrayHashMap(*Decl, void),
3504 outdated_decls: *std.AutoArrayHashMap(*Decl, void),
3461 decl_node: ast.Node.Index,3505 decl_node: ast.Node.Index,
3462 decl_i: usize,3506 decl_i: usize,
3463 tree: ast.Tree,3507 tree: ast.Tree,
...@@ -3489,7 +3533,7 @@ fn semaContainerFn(...@@ -3489,7 +3533,7 @@ fn semaContainerFn(
3489 try mod.failed_decls.putNoClobber(mod.gpa, decl, msg);3533 try mod.failed_decls.putNoClobber(mod.gpa, decl, msg);
3490 } else {3534 } else {
3491 if (!srcHashEql(decl.contents_hash, contents_hash)) {3535 if (!srcHashEql(decl.contents_hash, contents_hash)) {
3492 try mod.markOutdatedDecl(decl);3536 try outdated_decls.put(decl, {});
3493 decl.contents_hash = contents_hash;3537 decl.contents_hash = contents_hash;
3494 } else switch (mod.comp.bin_file.tag) {3538 } else switch (mod.comp.bin_file.tag) {
3495 .coff => {3539 .coff => {
...@@ -3524,6 +3568,7 @@ fn semaContainerVar(...@@ -3524,6 +3568,7 @@ fn semaContainerVar(
3524 mod: *Module,3568 mod: *Module,
3525 container_scope: *Scope.Container,3569 container_scope: *Scope.Container,
3526 deleted_decls: *std.AutoArrayHashMap(*Decl, void),3570 deleted_decls: *std.AutoArrayHashMap(*Decl, void),
3571 outdated_decls: *std.AutoArrayHashMap(*Decl, void),
3527 decl_node: ast.Node.Index,3572 decl_node: ast.Node.Index,
3528 decl_i: usize,3573 decl_i: usize,
3529 tree: ast.Tree,3574 tree: ast.Tree,
...@@ -3549,7 +3594,7 @@ fn semaContainerVar(...@@ -3549,7 +3594,7 @@ fn semaContainerVar(
3549 errdefer err_msg.destroy(mod.gpa);3594 errdefer err_msg.destroy(mod.gpa);
3550 try mod.failed_decls.putNoClobber(mod.gpa, decl, err_msg);3595 try mod.failed_decls.putNoClobber(mod.gpa, decl, err_msg);
3551 } else if (!srcHashEql(decl.contents_hash, contents_hash)) {3596 } else if (!srcHashEql(decl.contents_hash, contents_hash)) {
3552 try mod.markOutdatedDecl(decl);3597 try outdated_decls.put(decl, {});
3553 decl.contents_hash = contents_hash;3598 decl.contents_hash = contents_hash;
3554 }3599 }
3555 } else {3600 } else {
...@@ -3579,17 +3624,27 @@ fn semaContainerField(...@@ -3579,17 +3624,27 @@ fn semaContainerField(
3579 log.err("TODO: analyze container field", .{});3624 log.err("TODO: analyze container field", .{});
3580}3625}
35813626
3582pub fn deleteDecl(mod: *Module, decl: *Decl) !void {3627pub fn deleteDecl(
3628 mod: *Module,
3629 decl: *Decl,
3630 outdated_decls: ?*std.AutoArrayHashMap(*Decl, void),
3631) !void {
3583 const tracy = trace(@src());3632 const tracy = trace(@src());
3584 defer tracy.end();3633 defer tracy.end();
35853634
3586 try mod.deletion_set.ensureCapacity(mod.gpa, mod.deletion_set.items.len + decl.dependencies.items().len);3635 log.debug("deleting decl '{s}'", .{decl.name});
3636
3637 if (outdated_decls) |map| {
3638 _ = map.swapRemove(decl);
3639 try map.ensureCapacity(map.count() + decl.dependants.count());
3640 }
3641 try mod.deletion_set.ensureCapacity(mod.gpa, mod.deletion_set.count() +
3642 decl.dependencies.count());
35873643
3588 // Remove from the namespace it resides in. In the case of an anonymous Decl it will3644 // Remove from the namespace it resides in. In the case of an anonymous Decl it will
3589 // not be present in the set, and this does nothing.3645 // not be present in the set, and this does nothing.
3590 decl.container.removeDecl(decl);3646 decl.container.removeDecl(decl);
35913647
3592 log.debug("deleting decl '{s}'", .{decl.name});
3593 const name_hash = decl.fullyQualifiedNameHash();3648 const name_hash = decl.fullyQualifiedNameHash();
3594 mod.decl_table.removeAssertDiscard(name_hash);3649 mod.decl_table.removeAssertDiscard(name_hash);
3595 // Remove itself from its dependencies, because we are about to destroy the decl pointer.3650 // Remove itself from its dependencies, because we are about to destroy the decl pointer.
...@@ -3600,16 +3655,22 @@ pub fn deleteDecl(mod: *Module, decl: *Decl) !void {...@@ -3600,16 +3655,22 @@ pub fn deleteDecl(mod: *Module, decl: *Decl) !void {
3600 // We don't recursively perform a deletion here, because during the update,3655 // We don't recursively perform a deletion here, because during the update,
3601 // another reference to it may turn up.3656 // another reference to it may turn up.
3602 dep.deletion_flag = true;3657 dep.deletion_flag = true;
3603 mod.deletion_set.appendAssumeCapacity(dep);3658 mod.deletion_set.putAssumeCapacity(dep, {});
3604 }3659 }
3605 }3660 }
3606 // Anything that depends on this deleted decl certainly needs to be re-analyzed.3661 // Anything that depends on this deleted decl needs to be re-analyzed.
3607 for (decl.dependants.items()) |entry| {3662 for (decl.dependants.items()) |entry| {
3608 const dep = entry.key;3663 const dep = entry.key;
3609 dep.removeDependency(decl);3664 dep.removeDependency(decl);
3610 if (dep.analysis != .outdated) {3665 if (outdated_decls) |map| {
3611 // TODO Move this failure possibility to the top of the function.3666 map.putAssumeCapacity(dep, {});
3612 try mod.markOutdatedDecl(dep);3667 } else if (std.debug.runtime_safety) {
3668 // If `outdated_decls` is `null`, it means we're being called from
3669 // `Compilation` after `performAllTheWork` and we cannot queue up any
3670 // more work. `dep` must necessarily be another Decl that is no longer
3671 // being referenced, and will be in the `deletion_set`. Otherwise,
3672 // something has gone wrong.
3673 assert(mod.deletion_set.contains(dep));
3613 }3674 }
3614 }3675 }
3615 if (mod.failed_decls.swapRemove(decl)) |entry| {3676 if (mod.failed_decls.swapRemove(decl)) |entry| {
test/stage2/cbe.zig+53
...@@ -538,6 +538,45 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -538,6 +538,45 @@ pub fn addCases(ctx: *TestContext) !void {
538538
539 {539 {
540 var case = ctx.exeFromCompiledC("enums", .{});540 var case = ctx.exeFromCompiledC("enums", .{});
541
542 case.addError(
543 \\const E1 = packed enum { a, b, c };
544 \\const E2 = extern enum { a, b, c };
545 \\export fn foo() void {
546 \\ const x = E1.a;
547 \\}
548 \\export fn bar() void {
549 \\ const x = E2.a;
550 \\}
551 , &.{
552 ":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
553 ":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
554 });
555
556 // comptime and types are caught in AstGen.
557 case.addError(
558 \\const E1 = enum {
559 \\ a,
560 \\ comptime b,
561 \\ c,
562 \\};
563 \\const E2 = enum {
564 \\ a,
565 \\ b: i32,
566 \\ c,
567 \\};
568 \\export fn foo() void {
569 \\ const x = E1.a;
570 \\}
571 \\export fn bar() void {
572 \\ const x = E2.a;
573 \\}
574 , &.{
575 ":3:5: error: enum fields cannot be marked comptime",
576 ":8:8: error: enum fields do not have types",
577 });
578
579 // @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch
541 case.addCompareOutput(580 case.addCompareOutput(
542 \\const Number = enum { One, Two, Three };581 \\const Number = enum { One, Two, Three };
543 \\582 \\
...@@ -559,6 +598,20 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -559,6 +598,20 @@ pub fn addCases(ctx: *TestContext) !void {
559 \\ }598 \\ }
560 \\}599 \\}
561 , "");600 , "");
601
602 // Specifying alignment is a parse error.
603 case.addError(
604 \\const E1 = enum {
605 \\ a,
606 \\ b align(4),
607 \\ c,
608 \\};
609 \\export fn foo() void {
610 \\ const x = E1.a;
611 \\}
612 , &.{
613 ":3:7: error: expected ',', found 'align'",
614 });
562 }615 }
563616
564 ctx.c("empty start function", linux_x64,617 ctx.c("empty start function", linux_x64,
test/stage2/test.zig-42
...@@ -1598,46 +1598,4 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -1598,46 +1598,4 @@ pub fn addCases(ctx: *TestContext) !void {
1598 "",1598 "",
1599 );1599 );
1600 }1600 }
1601 {
1602 var case = ctx.exe("enum_literal -> enum", linux_x64);
1603
1604 case.addCompareOutput(
1605 \\const E = enum { a, b };
1606 \\export fn _start() noreturn {
1607 \\ const a: E = .a;
1608 \\ const b: E = .b;
1609 \\ exit();
1610 \\}
1611 \\fn exit() noreturn {
1612 \\ asm volatile ("syscall"
1613 \\ :
1614 \\ : [number] "{rax}" (231),
1615 \\ [arg1] "{rdi}" (0)
1616 \\ : "rcx", "r11", "memory"
1617 \\ );
1618 \\ unreachable;
1619 \\}
1620 ,
1621 "",
1622 );
1623 case.addError(
1624 \\export fn _start() noreturn {
1625 \\ const a: E = .c;
1626 \\ exit();
1627 \\}
1628 \\const E = enum { a, b };
1629 \\fn exit() noreturn {
1630 \\ asm volatile ("syscall"
1631 \\ :
1632 \\ : [number] "{rax}" (231),
1633 \\ [arg1] "{rdi}" (0)
1634 \\ : "rcx", "r11", "memory"
1635 \\ );
1636 \\ unreachable;
1637 \\}
1638 , &.{
1639 ":2:19: error: enum 'E' has no field named 'c'",
1640 ":5:11: note: enum declared here",
1641 });
1642 }
1643}1601}