Commit 7ba8641d19

mlugg <mlugg@mlugg.co.uk>
2024-03-09 00:59:56
Zcu: handle updates to file root struct
1 parent 42fedb3
Changed files (2)
src/Module.zig
@@ -2987,6 +2987,15 @@ pub fn mapOldZirToNew(
     }
 }
 
+/// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl.
+pub fn ensureFileAnalyzed(zcu: *Zcu, file: *File) SemaError!void {
+    if (file.root_decl == .none) {
+        return zcu.semaFile(file);
+    } else {
+        return zcu.ensureDeclAnalyzed(file.root_decl);
+    }
+}
+
 /// This ensures that the Decl will have an up-to-date Type and Value populated.
 /// However the resolution status of the Type may not be fully resolved.
 /// For example an inferred error set is not resolved until after `analyzeFnBody`.
@@ -3008,13 +3017,15 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
     // dependencies are all up-to-date.
 
     const decl_as_depender = InternPool.Depender.wrap(.{ .decl = decl_index });
-    const was_outdated = mod.outdated.swapRemove(decl_as_depender) or
+    const decl_was_outdated = mod.outdated.swapRemove(decl_as_depender) or
         mod.potentially_outdated.swapRemove(decl_as_depender);
 
-    if (was_outdated) {
+    if (decl_was_outdated) {
         _ = mod.outdated_ready.swapRemove(decl_as_depender);
     }
 
+    const was_outdated = mod.outdated_file_root.swapRemove(decl_index) or decl_was_outdated;
+
     switch (decl.analysis) {
         .in_progress => unreachable,
 
@@ -3050,6 +3061,14 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
             };
         }
 
+        if (mod.declIsRoot(decl_index)) {
+            const changed = try mod.semaFileUpdate(decl.getFileScope(mod), decl_was_outdated);
+            break :blk .{
+                .invalidate_decl_val = changed,
+                .invalidate_decl_ref = changed,
+            };
+        }
+
         break :blk mod.semaDecl(decl_index) catch |err| switch (err) {
             error.AnalysisFail => {
                 if (decl.analysis == .in_progress) {
@@ -3078,7 +3097,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
     };
 
     // TODO: we do not yet have separate dependencies for decl values vs types.
-    if (was_outdated) {
+    if (decl_was_outdated) {
         if (sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref) {
             // This dependency was marked as PO, meaning dependees were waiting
             // on its analysis result, and it has turned out to be outdated.
@@ -3359,13 +3378,70 @@ fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespa
     return wip_ty.finish(ip, decl_index, namespace_index.toOptional());
 }
 
-/// Regardless of the file status, will create a `Decl` so that we
-/// can track dependencies and re-analyze when the file becomes outdated.
-pub fn semaFile(mod: *Module, file: *File) SemaError!void {
+/// Re-analyze the root Decl of a file on an incremental update.
+/// If `type_outdated`, the struct type itself is considered outdated and is
+/// reconstructed at a new InternPool index. Otherwise, the namespace is just
+/// re-analyzed. Returns whether the decl's tyval was invalidated.
+fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool {
+    assert(file.root_decl != .none);
+
+    const decl = zcu.declPtr(file.root_decl);
+
+    if (file.status != .success_zir) {
+        if (decl.analysis == .file_failure) {
+            return false;
+        } else {
+            decl.analysis = .file_failure;
+            return true;
+        }
+    }
+
+    if (decl.analysis == .file_failure) {
+        // No struct type currently exists. Create one!
+        _ = try zcu.getFileRootStruct(file.root_decl, decl.src_namespace, file);
+        return true;
+    }
+
+    assert(decl.has_tv);
+    assert(decl.owns_tv);
+
+    if (type_outdated) {
+        // Invalidate the existing type, reusing the decl and namespace.
+        try zcu.intern_pool.remove(decl.val.toIntern());
+        decl.val = undefined;
+        _ = try zcu.getFileRootStruct(file.root_decl, decl.src_namespace, file);
+        return true;
+    }
+
+    // Only the struct's namespace is outdated.
+    // Preserve the type - just scan the namespace again.
+
+    const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended;
+    const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
+
+    var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len;
+    extra_index += @intFromEnum(small.has_fields_len);
+    const decls_len = if (small.has_decls_len) blk: {
+        const decls_len = file.zir.extra[extra_index];
+        extra_index += 1;
+        break :blk decls_len;
+    } else 0;
+    const decls = file.zir.bodySlice(extra_index, decls_len);
+
+    if (!type_outdated) {
+        try zcu.scanNamespace(decl.src_namespace, decls, decl);
+    }
+
+    return false;
+}
+
+/// Regardless of the file status, will create a `Decl` if none exists so that we can track
+/// dependencies and re-analyze when the file becomes outdated.
+fn semaFile(mod: *Module, file: *File) SemaError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    if (file.root_decl != .none) return;
+    assert(file.root_decl == .none);
 
     const gpa = mod.gpa;
     log.debug("semaFile mod={s} sub_file_path={s}", .{
@@ -3432,9 +3508,6 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
         },
         .incremental => {},
     }
-
-    // Since this is our first time analyzing this file, there can be no dependencies on
-    // its root Decl. Thus, we do not need to invalidate any dependencies.
 }
 
 const SemaDeclResult = packed struct {
@@ -3455,11 +3528,9 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
         return error.AnalysisFail;
     }
 
-    if (mod.declIsRoot(decl_index)) {
-        // This comes from an `analyze_decl` job on an incremental update where
-        // this file changed.
-        @panic("TODO: update root Decl of modified file");
-    } else if (decl.owns_tv) {
+    assert(!mod.declIsRoot(decl_index));
+
+    if (decl.owns_tv) {
         // We are re-analyzing an owner Decl (for a function or a namespace type).
         @panic("TODO: update owner Decl");
     }
src/Sema.zig
@@ -5883,7 +5883,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
     mod.astGenFile(result.file) catch |err|
         return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
 
-    try mod.semaFile(result.file);
+    try mod.ensureFileAnalyzed(result.file);
     const file_root_decl_index = result.file.root_decl.unwrap().?;
     return sema.analyzeDeclVal(parent_block, src, file_root_decl_index);
 }
@@ -13705,7 +13705,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
             return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ operand, @errorName(err) });
         },
     };
-    try mod.semaFile(result.file);
+    try mod.ensureFileAnalyzed(result.file);
     const file_root_decl_index = result.file.root_decl.unwrap().?;
     return sema.analyzeDeclVal(block, operand_src, file_root_decl_index);
 }