authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-12 12:22:58+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-12 21:27:16+01:00
log0978566db8b7ed2b730cf01a7d359e9b52ec66ec
tree5cc2665bd7447dbd6a681dde31ff0efce79eac50
parent01cc1a58675806b72580e094609f8dde0019d6ea

incremental: handle loss of main struct instruction

My changes to how incremental compilation handles container types mean that, at least for now, it is possible for the ZIR `.main_struct_inst` of a source file to be lost (this happens if the number of top-level fields in a file changes for instance). I missed a few things which needed changing to account for this, which could lead to crashes with certain (trivial) changes---oops! Adds two new incremental test cases. They are currently disabled for wasm32-wasi-selfhosted because they both trigger a crash in the WASM backend.

8 files changed, 265 insertions(+), 39 deletions(-)

src/Compilation.zig+4-4
...@@ -3649,7 +3649,7 @@ const Header = extern struct {...@@ -3649,7 +3649,7 @@ const Header = extern struct {
3649 type_layout_deps_len: u32,3649 type_layout_deps_len: u32,
3650 struct_defaults_deps_len: u32,3650 struct_defaults_deps_len: u32,
3651 func_ies_deps_len: u32,3651 func_ies_deps_len: u32,
3652 zon_file_deps_len: u32,3652 source_file_deps_len: u32,
3653 embed_file_deps_len: u32,3653 embed_file_deps_len: u32,
3654 namespace_deps_len: u32,3654 namespace_deps_len: u32,
3655 namespace_name_deps_len: u32,3655 namespace_name_deps_len: u32,
...@@ -3699,7 +3699,7 @@ pub fn saveState(comp: *Compilation) !void {...@@ -3699,7 +3699,7 @@ pub fn saveState(comp: *Compilation) !void {
3699 .type_layout_deps_len = @intCast(ip.type_layout_deps.count()),3699 .type_layout_deps_len = @intCast(ip.type_layout_deps.count()),
3700 .struct_defaults_deps_len = @intCast(ip.struct_defaults_deps.count()),3700 .struct_defaults_deps_len = @intCast(ip.struct_defaults_deps.count()),
3701 .func_ies_deps_len = @intCast(ip.func_ies_deps.count()),3701 .func_ies_deps_len = @intCast(ip.func_ies_deps.count()),
3702 .zon_file_deps_len = @intCast(ip.zon_file_deps.count()),3702 .source_file_deps_len = @intCast(ip.source_file_deps.count()),
3703 .embed_file_deps_len = @intCast(ip.embed_file_deps.count()),3703 .embed_file_deps_len = @intCast(ip.embed_file_deps.count()),
3704 .namespace_deps_len = @intCast(ip.namespace_deps.count()),3704 .namespace_deps_len = @intCast(ip.namespace_deps.count()),
3705 .namespace_name_deps_len = @intCast(ip.namespace_name_deps.count()),3705 .namespace_name_deps_len = @intCast(ip.namespace_name_deps.count()),
...@@ -3738,8 +3738,8 @@ pub fn saveState(comp: *Compilation) !void {...@@ -3738,8 +3738,8 @@ pub fn saveState(comp: *Compilation) !void {
3738 addBuf(&bufs, @ptrCast(ip.struct_defaults_deps.values()));3738 addBuf(&bufs, @ptrCast(ip.struct_defaults_deps.values()));
3739 addBuf(&bufs, @ptrCast(ip.func_ies_deps.keys()));3739 addBuf(&bufs, @ptrCast(ip.func_ies_deps.keys()));
3740 addBuf(&bufs, @ptrCast(ip.func_ies_deps.values()));3740 addBuf(&bufs, @ptrCast(ip.func_ies_deps.values()));
3741 addBuf(&bufs, @ptrCast(ip.zon_file_deps.keys()));3741 addBuf(&bufs, @ptrCast(ip.source_file_deps.keys()));
3742 addBuf(&bufs, @ptrCast(ip.zon_file_deps.values()));3742 addBuf(&bufs, @ptrCast(ip.source_file_deps.values()));
3743 addBuf(&bufs, @ptrCast(ip.embed_file_deps.keys()));3743 addBuf(&bufs, @ptrCast(ip.embed_file_deps.keys()));
3744 addBuf(&bufs, @ptrCast(ip.embed_file_deps.values()));3744 addBuf(&bufs, @ptrCast(ip.embed_file_deps.values()));
3745 addBuf(&bufs, @ptrCast(ip.namespace_deps.keys()));3745 addBuf(&bufs, @ptrCast(ip.namespace_deps.keys()));
src/IncrementalDebugServer.zig+1-1
...@@ -305,7 +305,7 @@ fn handleCommand(zcu: *Zcu, w: *Io.Writer, cmd_str: []const u8, arg_str: []const...@@ -305,7 +305,7 @@ fn handleCommand(zcu: *Zcu, w: *Io.Writer, cmd_str: []const u8, arg_str: []const
305 for (unit_info.deps.items, 0..) |dependee, i| {305 for (unit_info.deps.items, 0..) |dependee, i| {
306 try w.print("[{d}] ", .{i});306 try w.print("[{d}] ", .{i});
307 switch (dependee) {307 switch (dependee) {
308 .src_hash, .namespace, .namespace_name, .zon_file, .embed_file => try w.print("{f}", .{zcu.fmtDependee(dependee)}),308 .src_hash, .namespace, .namespace_name, .source_file, .embed_file => try w.print("{f}", .{zcu.fmtDependee(dependee)}),
309 .nav_val, .nav_ty => |nav| try w.print("{t} {d}", .{ dependee, @intFromEnum(nav) }),309 .nav_val, .nav_ty => |nav| try w.print("{t} {d}", .{ dependee, @intFromEnum(nav) }),
310 .type_layout, .struct_defaults, .func_ies => |ip_index| try w.print("{t} {d}", .{ dependee, @intFromEnum(ip_index) }),310 .type_layout, .struct_defaults, .func_ies => |ip_index| try w.print("{t} {d}", .{ dependee, @intFromEnum(ip_index) }),
311 .memoized_state => |stage| try w.print("memoized_state {s}", .{@tagName(stage)}),311 .memoized_state => |stage| try w.print("memoized_state {s}", .{@tagName(stage)}),
src/InternPool.zig+19-14
...@@ -57,9 +57,14 @@ type_layout_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index),...@@ -57,9 +57,14 @@ type_layout_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index),
57/// Dependencies on the resolved default field values of a `struct` type.57/// Dependencies on the resolved default field values of a `struct` type.
58/// Value is index into `dep_entries` of the first dependency on this type's inits.58/// Value is index into `dep_entries` of the first dependency on this type's inits.
59struct_defaults_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index),59struct_defaults_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index),
60/// Dependencies on a ZON file. Triggered by `@import` of ZON.60/// Dependencies on a Zig or ZON source file. Triggered by `@import`.
61/// Value is index into `dep_entries` of the first dependency on this ZON file.61/// * For ZON source files, the dependency is invalidated if the file changes at all. The `@import`
62zon_file_deps: std.AutoArrayHashMapUnmanaged(FileIndex, DepEntry.Index),62/// must be re-analyzed to return the new data structure.
63/// * For Zig source files, the dependency is invalidated if the file's root struct type changes
64/// (which can only happen because the `.main_struct_inst` got lost). The `@import` must be
65/// re-analyzed to return the new type.
66/// Value is index into `dep_entries` of the first dependency on this Zig/ZON file.
67source_file_deps: std.AutoArrayHashMapUnmanaged(FileIndex, DepEntry.Index),
63/// Dependencies on an embedded file.68/// Dependencies on an embedded file.
64/// Introduced by `@embedFile`; invalidated when the file changes.69/// Introduced by `@embedFile`; invalidated when the file changes.
65/// Value is index into `dep_entries` of the first dependency on this `Zcu.EmbedFile`.70/// Value is index into `dep_entries` of the first dependency on this `Zcu.EmbedFile`.
...@@ -112,7 +117,7 @@ pub const empty: InternPool = .{...@@ -112,7 +117,7 @@ pub const empty: InternPool = .{
112 .func_ies_deps = .empty,117 .func_ies_deps = .empty,
113 .type_layout_deps = .empty,118 .type_layout_deps = .empty,
114 .struct_defaults_deps = .empty,119 .struct_defaults_deps = .empty,
115 .zon_file_deps = .empty,120 .source_file_deps = .empty,
116 .embed_file_deps = .empty,121 .embed_file_deps = .empty,
117 .namespace_deps = .empty,122 .namespace_deps = .empty,
118 .namespace_name_deps = .empty,123 .namespace_name_deps = .empty,
...@@ -859,7 +864,7 @@ pub const Dependee = union(enum) {...@@ -859,7 +864,7 @@ pub const Dependee = union(enum) {
859 func_ies: Index,864 func_ies: Index,
860 type_layout: Index,865 type_layout: Index,
861 struct_defaults: Index,866 struct_defaults: Index,
862 zon_file: FileIndex,867 source_file: FileIndex,
863 embed_file: Zcu.EmbedFile.Index,868 embed_file: Zcu.EmbedFile.Index,
864 namespace: TrackedInst.Index,869 namespace: TrackedInst.Index,
865 namespace_name: NamespaceNameKey,870 namespace_name: NamespaceNameKey,
...@@ -913,7 +918,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI...@@ -913,7 +918,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI
913 .func_ies => |x| ip.func_ies_deps.get(x),918 .func_ies => |x| ip.func_ies_deps.get(x),
914 .type_layout => |x| ip.type_layout_deps.get(x),919 .type_layout => |x| ip.type_layout_deps.get(x),
915 .struct_defaults => |x| ip.struct_defaults_deps.get(x),920 .struct_defaults => |x| ip.struct_defaults_deps.get(x),
916 .zon_file => |x| ip.zon_file_deps.get(x),921 .source_file => |x| ip.source_file_deps.get(x),
917 .embed_file => |x| ip.embed_file_deps.get(x),922 .embed_file => |x| ip.embed_file_deps.get(x),
918 .namespace => |x| ip.namespace_deps.get(x),923 .namespace => |x| ip.namespace_deps.get(x),
919 .namespace_name => |x| ip.namespace_name_deps.get(x),924 .namespace_name => |x| ip.namespace_name_deps.get(x),
...@@ -988,7 +993,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend...@@ -988,7 +993,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend
988 .func_ies => ip.func_ies_deps,993 .func_ies => ip.func_ies_deps,
989 .type_layout => ip.type_layout_deps,994 .type_layout => ip.type_layout_deps,
990 .struct_defaults => ip.struct_defaults_deps,995 .struct_defaults => ip.struct_defaults_deps,
991 .zon_file => ip.zon_file_deps,996 .source_file => ip.source_file_deps,
992 .embed_file => ip.embed_file_deps,997 .embed_file => ip.embed_file_deps,
993 .namespace => ip.namespace_deps,998 .namespace => ip.namespace_deps,
994 .namespace_name => ip.namespace_name_deps,999 .namespace_name => ip.namespace_name_deps,
...@@ -6477,7 +6482,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator, io: Io) void {...@@ -6477,7 +6482,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator, io: Io) void {
6477 ip.func_ies_deps.deinit(gpa);6482 ip.func_ies_deps.deinit(gpa);
6478 ip.type_layout_deps.deinit(gpa);6483 ip.type_layout_deps.deinit(gpa);
6479 ip.struct_defaults_deps.deinit(gpa);6484 ip.struct_defaults_deps.deinit(gpa);
6480 ip.zon_file_deps.deinit(gpa);6485 ip.source_file_deps.deinit(gpa);
6481 ip.embed_file_deps.deinit(gpa);6486 ip.embed_file_deps.deinit(gpa);
6482 ip.namespace_deps.deinit(gpa);6487 ip.namespace_deps.deinit(gpa);
6483 ip.namespace_name_deps.deinit(gpa);6488 ip.namespace_name_deps.deinit(gpa);
...@@ -10643,7 +10648,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {...@@ -10643,7 +10648,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
10643 const func_ies_deps_len = ip.func_ies_deps.count();10648 const func_ies_deps_len = ip.func_ies_deps.count();
10644 const type_layout_deps_len = ip.type_layout_deps.count();10649 const type_layout_deps_len = ip.type_layout_deps.count();
10645 const struct_defaults_deps_len = ip.struct_defaults_deps.count();10650 const struct_defaults_deps_len = ip.struct_defaults_deps.count();
10646 const zon_file_deps_len = ip.zon_file_deps.count();10651 const source_file_deps_len = ip.source_file_deps.count();
10647 const embed_file_deps_len = ip.embed_file_deps.count();10652 const embed_file_deps_len = ip.embed_file_deps.count();
10648 const namespace_deps_len = ip.namespace_deps.count();10653 const namespace_deps_len = ip.namespace_deps.count();
10649 const namespace_name_deps_len = ip.namespace_name_deps.count();10654 const namespace_name_deps_len = ip.namespace_name_deps.count();
...@@ -10654,7 +10659,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {...@@ -10654,7 +10659,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
10654 const func_ies_deps_size = func_ies_deps_len * 8;10659 const func_ies_deps_size = func_ies_deps_len * 8;
10655 const type_layout_deps_size = type_layout_deps_len * 8;10660 const type_layout_deps_size = type_layout_deps_len * 8;
10656 const struct_defaults_deps_size = struct_defaults_deps_len * 8;10661 const struct_defaults_deps_size = struct_defaults_deps_len * 8;
10657 const zon_file_deps_size = zon_file_deps_len * 8;10662 const source_file_deps_size = source_file_deps_len * 8;
10658 const embed_file_deps_size = embed_file_deps_len * 8;10663 const embed_file_deps_size = embed_file_deps_len * 8;
10659 const namespace_deps_size = namespace_deps_len * 8;10664 const namespace_deps_size = namespace_deps_len * 8;
10660 const namespace_name_deps_size = namespace_name_deps_len * (@sizeOf(NamespaceNameKey) + 4);10665 const namespace_name_deps_size = namespace_name_deps_len * (@sizeOf(NamespaceNameKey) + 4);
...@@ -10668,14 +10673,14 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {...@@ -10668,14 +10673,14 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
10668 \\ {d} func_ies: {d} bytes10673 \\ {d} func_ies: {d} bytes
10669 \\ {d} type_layout: {d} bytes10674 \\ {d} type_layout: {d} bytes
10670 \\ {d} struct_defaults: {d} bytes10675 \\ {d} struct_defaults: {d} bytes
10671 \\ {d} zon_file: {d} bytes10676 \\ {d} source_file: {d} bytes
10672 \\ {d} embed_file: {d} bytes10677 \\ {d} embed_file: {d} bytes
10673 \\ {d} namespace: {d} bytes10678 \\ {d} namespace: {d} bytes
10674 \\ {d} namespace_name: {d} bytes10679 \\ {d} namespace_name: {d} bytes
10675 \\10680 \\
10676 , .{10681 , .{
10677 dep_entries_size + src_hash_deps_size + nav_val_deps_size + nav_ty_deps_size +10682 dep_entries_size + src_hash_deps_size + nav_val_deps_size + nav_ty_deps_size +
10678 func_ies_deps_size + type_layout_deps_size + struct_defaults_deps_size + zon_file_deps_size +10683 func_ies_deps_size + type_layout_deps_size + struct_defaults_deps_size + source_file_deps_size +
10679 embed_file_deps_size + namespace_deps_size + namespace_name_deps_size,10684 embed_file_deps_size + namespace_deps_size + namespace_name_deps_size,
10680 dep_entries_len,10685 dep_entries_len,
10681 dep_entries_size,10686 dep_entries_size,
...@@ -10691,8 +10696,8 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {...@@ -10691,8 +10696,8 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
10691 type_layout_deps_size,10696 type_layout_deps_size,
10692 struct_defaults_deps_len,10697 struct_defaults_deps_len,
10693 struct_defaults_deps_size,10698 struct_defaults_deps_size,
10694 zon_file_deps_len,10699 source_file_deps_len,
10695 zon_file_deps_size,10700 source_file_deps_size,
10696 embed_file_deps_len,10701 embed_file_deps_len,
10697 embed_file_deps_size,10702 embed_file_deps_size,
10698 namespace_deps_len,10703 namespace_deps_len,
src/Sema.zig+2-2
...@@ -13011,6 +13011,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air....@@ -13011,6 +13011,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
13011 };13011 };
13012 const file_index = result.file;13012 const file_index = result.file;
13013 const file = zcu.fileByIndex(file_index);13013 const file = zcu.fileByIndex(file_index);
13014 try sema.declareDependency(.{ .source_file = file_index });
13014 switch (file.getMode()) {13015 switch (file.getMode()) {
13015 .zig => {13016 .zig => {
13016 try pt.ensureFilePopulated(file_index);13017 try pt.ensureFilePopulated(file_index);
...@@ -13028,8 +13029,6 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air....@@ -13028,8 +13029,6 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
13028 if (res_ty.isGenericPoison()) break :b .none;13029 if (res_ty.isGenericPoison()) break :b .none;
13029 break :b res_ty.toIntern();13030 break :b res_ty.toIntern();
13030 };13031 };
13031
13032 try sema.declareDependency(.{ .zon_file = file_index });
13033 const interned = try LowerZon.run(13032 const interned = try LowerZon.run(
13034 sema,13033 sema,
13035 file,13034 file,
...@@ -34084,6 +34083,7 @@ pub fn analyzeMemoizedState(sema: *Sema, stage: InternPool.MemoizedStateStage) C...@@ -34084,6 +34083,7 @@ pub fn analyzeMemoizedState(sema: *Sema, stage: InternPool.MemoizedStateStage) C
34084 // Get the main struct type of the root source file of `std`. No need for a reference entry34083 // Get the main struct type of the root source file of `std`. No need for a reference entry
34085 // because `std` is always an analysis root.34084 // because `std` is always an analysis root.
34086 const std_file_index = zcu.module_roots.get(zcu.std_mod).?.unwrap().?;34085 const std_file_index = zcu.module_roots.get(zcu.std_mod).?.unwrap().?;
34086 try sema.declareDependency(.{ .source_file = std_file_index });
34087 try pt.ensureFilePopulated(std_file_index);34087 try pt.ensureFilePopulated(std_file_index);
34088 const std_type: Type = .fromInterned(zcu.fileRootType(std_file_index));34088 const std_type: Type = .fromInterned(zcu.fileRootType(std_file_index));
34089 break :block .{34089 break :block .{
src/Zcu.zig+3-3
...@@ -1014,7 +1014,7 @@ pub const File = struct {...@@ -1014,7 +1014,7 @@ pub const File = struct {
1014 /// changed -- this field is just a simple boolean.1014 /// changed -- this field is just a simple boolean.
1015 ///1015 ///
1016 /// When `zoir` is updated, this field is set to `true`. In `updateZirRefs`, if this is `true`,1016 /// When `zoir` is updated, this field is set to `true`. In `updateZirRefs`, if this is `true`,
1017 /// we invalidate the corresponding `zon_file` dependency, and reset it to `false`.1017 /// we invalidate the corresponding `source_file` dependency, and reset it to `false`.
1018 zoir_invalidated: bool,1018 zoir_invalidated: bool,
10191019
1020 pub const Path = struct {1020 pub const Path = struct {
...@@ -4496,9 +4496,9 @@ fn formatDependee(data: FormatDependee, writer: *Io.Writer) Io.Writer.Error!void...@@ -4496,9 +4496,9 @@ fn formatDependee(data: FormatDependee, writer: *Io.Writer) Io.Writer.Error!void
4496 const fqn = ip.getNav(ip.indexToKey(ip_index).func.owner_nav).fqn;4496 const fqn = ip.getNav(ip.indexToKey(ip_index).func.owner_nav).fqn;
4497 return writer.print("func_ies('{f}')", .{fqn.fmt(ip)});4497 return writer.print("func_ies('{f}')", .{fqn.fmt(ip)});
4498 },4498 },
4499 .zon_file => |file| {4499 .source_file => |file| {
4500 const file_path = zcu.fileByIndex(file).path;4500 const file_path = zcu.fileByIndex(file).path;
4501 return writer.print("zon_file('{f}')", .{file_path.fmt(zcu.comp)});4501 return writer.print("source_file('{f}')", .{file_path.fmt(zcu.comp)});
4502 },4502 },
4503 .embed_file => |ef_idx| {4503 .embed_file => |ef_idx| {
4504 const ef = ef_idx.get(zcu);4504 const ef = ef_idx.get(zcu);
src/Zcu/PerThread.zig+37-15
...@@ -838,7 +838,7 @@ fn updateZirRefs(pt: Zcu.PerThread) (Io.Cancelable || Allocator.Error)!void {...@@ -838,7 +838,7 @@ fn updateZirRefs(pt: Zcu.PerThread) (Io.Cancelable || Allocator.Error)!void {
838 .zig => {}, // logic below838 .zig => {}, // logic below
839 .zon => {839 .zon => {
840 if (file.zoir_invalidated) {840 if (file.zoir_invalidated) {
841 try zcu.markDependeeOutdated(.not_marked_po, .{ .zon_file = file_index });841 try zcu.markDependeeOutdated(.not_marked_po, .{ .source_file = file_index });
842 file.zoir_invalidated = false;842 file.zoir_invalidated = false;
843 }843 }
844 continue;844 continue;
...@@ -988,8 +988,8 @@ fn updateZirRefs(pt: Zcu.PerThread) (Io.Cancelable || Allocator.Error)!void {...@@ -988,8 +988,8 @@ fn updateZirRefs(pt: Zcu.PerThread) (Io.Cancelable || Allocator.Error)!void {
988 // be re-analyzed (causing the struct's namespace to be re-scanned). It's fine to do this988 // be re-analyzed (causing the struct's namespace to be re-scanned). It's fine to do this
989 // now because this work is fast (no actual Sema work is happening, we're just updating the989 // now because this work is fast (no actual Sema work is happening, we're just updating the
990 // namespace contents). We must do this after updating ZIR refs above, since `scanNamespace`990 // namespace contents). We must do this after updating ZIR refs above, since `scanNamespace`
991 // will track some instructions.991 // calls will track some instructions.
992 try pt.updateFileNamespace(file_index);992 try pt.updateFileRootStructType(file_index);
993 }993 }
994}994}
995995
...@@ -2350,27 +2350,49 @@ fn analyzeFuncBody(...@@ -2350,27 +2350,49 @@ fn analyzeFuncBody(
2350 return .{ .ies_outdated = ies_outdated };2350 return .{ .ies_outdated = ies_outdated };
2351}2351}
23522352
2353/// Re-scan the namespace of a file's root struct type on an incremental update.2353/// The given file has been modified on this incremental update, so if it has a populated root
2354/// The file must have successfully populated ZIR.2354/// struct type, either re-scan its namespace, or clear it and invalidate dependencies if the
2355/// If the file's root struct type is not populated (the file is unreferenced), nothing is done.2355/// type is no longer valid. See comments in body for more details.
2356/// This is called by `updateZirRefs` for all updated files before the main work loop.2356///
2357/// This function does not perform any semantic analysis.2357/// Called by `updateZirRefs` for all updated Zig source files before the main update loop.
2358fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void {2358///
2359/// Asserts that the file has successfully populated ZIR.
2360fn updateFileRootStructType(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void {
2359 const zcu = pt.zcu;2361 const zcu = pt.zcu;
2362 const ip = &zcu.intern_pool;
23602363
2361 const file = zcu.fileByIndex(file_index);2364 const file = zcu.fileByIndex(file_index);
2362 const file_root_type = zcu.fileRootType(file_index);2365 const file_root_type = zcu.fileRootType(file_index);
2363 if (file_root_type == .none) return;2366 if (file_root_type == .none) {
2367 // We haven't analyzed any `@import` of this file so far, so there's nothing to update. If
2368 // an `@import` gets analyzed, then `ensureFilePopulated` will create the root struct type
2369 // and scan the namespace.
2370 return;
2371 }
23642372
2365 log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{2373 const loaded_struct = ip.loadStructType(file_root_type);
2374
2375 log.debug("updateFileRootStructType mod={s} sub_file_path={s}", .{
2366 file.mod.?.fully_qualified_name,2376 file.mod.?.fully_qualified_name,
2367 file.sub_file_path,2377 file.sub_file_path,
2368 });2378 });
23692379
2370 const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu);2380 if (loaded_struct.zir_index.resolve(ip) == null) {
2371 const decls = file.zir.?.getStructDecl(.main_struct_inst).decls;2381 // The file's root struct decl has been lost, so a new struct type must be interned at a new
2372 try pt.scanNamespace(namespace_index, decls);2382 // `InternPool.Index`. Clear the file's root type so that `ensureFilePopulated` will do that
2373 zcu.namespacePtr(namespace_index).generation = zcu.generation;2383 // work, and invalidate dependencies on this file to force re-analysis of `@import` sites.
2384 zcu.setFileRootType(file_index, .none);
2385 try zcu.markDependeeOutdated(.not_marked_po, .{ .source_file = file_index });
2386 } else {
2387 // The existing struct type is valid, but the namespace contents might have changed. For
2388 // most struct types, that would cause the surrounding declaration to be invalidated which
2389 // causes `Sema.zirStructType` (or whatever) to call `ensureNamespaceUpToDate`. However,
2390 // there is no "surrounding declaration" for the root struct type of a Zig source file, so
2391 // update this namespace now.
2392 const decls = file.zir.?.getStructDecl(.main_struct_inst).decls;
2393 try pt.scanNamespace(loaded_struct.namespace, decls);
2394 zcu.namespacePtr(loaded_struct.namespace).generation = zcu.generation;
2395 }
2374}2396}
23752397
2376/// Called by AstGen worker threads when an import is seen. If `new_file` is returned, the caller is2398/// Called by AstGen worker threads when an import is seen. If `new_file` is returned, the caller is
test/incremental/add_remove_struct_fields created+98
...@@ -0,0 +1,98 @@
1#target=x86_64-linux-selfhosted
2#target=x86_64-windows-selfhosted
3#target=x86_64-linux-cbe
4#target=x86_64-windows-cbe
5//#target=wasm32-wasi-selfhosted
6#update=initial version
7#file=main.zig
8const S = struct { x: u8 };
9pub fn main(init: std.process.Init) !void {
10 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
11 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
12 error.WriteFailed => return stdout_writer.err.?,
13 };
14 printOneField(&stdout_writer.interface) catch |err| switch (err) {
15 error.WriteFailed => return stdout_writer.err.?,
16 };
17}
18fn printFieldCount(w: *Writer) Writer.Error!void {
19 try w.print("{d} ", .{@typeInfo(S).@"struct".fields.len});
20}
21fn printOneField(w: *Writer) Writer.Error!void {
22 const val: S = .{ .x = 100 };
23 try w.print("{d}\n", .{val.x});
24}
25const std = @import("std");
26const Writer = std.Io.Writer;
27#expect_stdout="1 100\n"
28
29#update=add a field
30#file=main.zig
31const S = struct { x: u8, y: u16 = 200 };
32pub fn main(init: std.process.Init) !void {
33 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
34 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
35 error.WriteFailed => return stdout_writer.err.?,
36 };
37 printOneField(&stdout_writer.interface) catch |err| switch (err) {
38 error.WriteFailed => return stdout_writer.err.?,
39 };
40}
41fn printFieldCount(w: *Writer) Writer.Error!void {
42 try w.print("{d} ", .{@typeInfo(S).@"struct".fields.len});
43}
44fn printOneField(w: *Writer) Writer.Error!void {
45 const val: S = .{ .x = 100 };
46 try w.print("{d}\n", .{val.x});
47}
48const std = @import("std");
49const Writer = std.Io.Writer;
50#expect_stdout="2 100\n"
51
52#update=remove all fields
53#file=main.zig
54const S = struct {};
55pub fn main(init: std.process.Init) !void {
56 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
57 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
58 error.WriteFailed => return stdout_writer.err.?,
59 };
60 printOneField(&stdout_writer.interface) catch |err| switch (err) {
61 error.WriteFailed => return stdout_writer.err.?,
62 };
63}
64fn printFieldCount(w: *Writer) Writer.Error!void {
65 try w.print("{d} ", .{@typeInfo(S).@"struct".fields.len});
66}
67fn printOneField(w: *Writer) Writer.Error!void {
68 const val: S = .{ .x = 100 };
69 try w.print("{d}\n", .{val.x});
70}
71const std = @import("std");
72const Writer = std.Io.Writer;
73#expect_error=main.zig:15:24: error: no field named 'x' in struct 'main.S'
74#expect_error=main.zig:1:11: note: struct declared here
75
76#update=remove reference to non-existent field
77#file=main.zig
78const S = struct {};
79pub fn main(init: std.process.Init) !void {
80 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
81 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
82 error.WriteFailed => return stdout_writer.err.?,
83 };
84 printOneField(&stdout_writer.interface) catch |err| switch (err) {
85 error.WriteFailed => return stdout_writer.err.?,
86 };
87}
88fn printFieldCount(w: *Writer) Writer.Error!void {
89 try w.print("{d} ", .{@typeInfo(S).@"struct".fields.len});
90}
91fn printOneField(w: *Writer) Writer.Error!void {
92 //const val: S = .{ .x = 100 };
93 //try w.print("{d}\n", .{val.x});
94 try w.writeAll("<no fields>\n");
95}
96const std = @import("std");
97const Writer = std.Io.Writer;
98#expect_stdout="0 <no fields>\n"
test/incremental/add_remove_toplevel_fields created+101
...@@ -0,0 +1,101 @@
1#target=x86_64-linux-selfhosted
2#target=x86_64-windows-selfhosted
3#target=x86_64-linux-cbe
4#target=x86_64-windows-cbe
5//#target=wasm32-wasi-selfhosted
6#update=initial version
7#file=main.zig
8const S = @This();
9x: u8,
10pub fn main(init: std.process.Init) !void {
11 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
12 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
13 error.WriteFailed => return stdout_writer.err.?,
14 };
15 printOneField(&stdout_writer.interface) catch |err| switch (err) {
16 error.WriteFailed => return stdout_writer.err.?,
17 };
18}
19fn printFieldCount(w: *Writer) Writer.Error!void {
20 try w.print("{d} ", .{@typeInfo(S).@"struct".fields.len});
21}
22fn printOneField(w: *Writer) Writer.Error!void {
23 const val: S = .{ .x = 100 };
24 try w.print("{d}\n", .{val.x});
25}
26const std = @import("std");
27const Writer = std.Io.Writer;
28#expect_stdout="1 100\n"
29
30#update=add a field
31#file=main.zig
32const S = @This();
33x: u8,
34y: u16 = 200,
35pub fn main(init: std.process.Init) !void {
36 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
37 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
38 error.WriteFailed => return stdout_writer.err.?,
39 };
40 printOneField(&stdout_writer.interface) catch |err| switch (err) {
41 error.WriteFailed => return stdout_writer.err.?,
42 };
43}
44fn printFieldCount(w: *Writer) Writer.Error!void {
45 try w.print("{d} ", .{@typeInfo(S).@"struct".fields.len});
46}
47fn printOneField(w: *Writer) Writer.Error!void {
48 const val: S = .{ .x = 100 };
49 try w.print("{d}\n", .{val.x});
50}
51const std = @import("std");
52const Writer = std.Io.Writer;
53#expect_stdout="2 100\n"
54
55#update=remove all fields
56#file=main.zig
57const S = @This();
58pub fn main(init: std.process.Init) !void {
59 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
60 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
61 error.WriteFailed => return stdout_writer.err.?,
62 };
63 printOneField(&stdout_writer.interface) catch |err| switch (err) {
64 error.WriteFailed => return stdout_writer.err.?,
65 };
66}
67fn printFieldCount(w: *Writer) Writer.Error!void {
68 try w.print("{d} ", .{@typeInfo(S).@"struct".fields.len});
69}
70fn printOneField(w: *Writer) Writer.Error!void {
71 const val: S = .{ .x = 100 };
72 try w.print("{d}\n", .{val.x});
73}
74const std = @import("std");
75const Writer = std.Io.Writer;
76#expect_error=main.zig:15:24: error: no field named 'x' in struct 'main'
77#expect_error=main.zig:1:1: note: struct declared here
78
79#update=remove reference to non-existent field
80#file=main.zig
81const S = @This();
82pub fn main(init: std.process.Init) !void {
83 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
84 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
85 error.WriteFailed => return stdout_writer.err.?,
86 };
87 printOneField(&stdout_writer.interface) catch |err| switch (err) {
88 error.WriteFailed => return stdout_writer.err.?,
89 };
90}
91fn printFieldCount(w: *Writer) Writer.Error!void {
92 try w.print("{d} ", .{@typeInfo(S).@"struct".fields.len});
93}
94fn printOneField(w: *Writer) Writer.Error!void {
95 //const val: S = .{ .x = 100 };
96 //try w.print("{d}\n", .{val.x});
97 try w.writeAll("<no fields>\n");
98}
99const std = @import("std");
100const Writer = std.Io.Writer;
101#expect_stdout="0 <no fields>\n"