Commit 93a5bd262d

mlugg <mlugg@mlugg.co.uk>
2024-08-18 14:35:35
frontend: removed resolved IES data for outdated functions
Without this, incremental updates which would change inferred error sets fail, since they assume the IES is resolved and equals the old set, resulting in false positive compile errors when e.g. coercing to an IES.
1 parent 1045537
src/Zcu/PerThread.zig
@@ -2072,6 +2072,9 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!
     errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
 
     func.setAnalysisState(ip, .analyzed);
+    if (func.analysisUnordered(ip).inferred_error_set) {
+        func.setResolvedErrorSet(ip, .none);
+    }
 
     // This is the `Cau` corresponding to the `declaration` instruction which the function or its generic owner originates from.
     const decl_cau = ip.getCau(cau: {
@@ -2278,7 +2281,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!
             else => |e| return e,
         };
         assert(ies.resolved != .none);
-        ip.funcSetIesResolved(func_index, ies.resolved);
+        func.setResolvedErrorSet(ip, ies.resolved);
     }
 
     assert(zcu.analysis_in_progress.swapRemove(anal_unit));
src/InternPool.zig
@@ -2160,6 +2160,14 @@ pub const Key = union(enum) {
         pub fn resolvedErrorSetUnordered(func: Func, ip: *const InternPool) Index {
             return @atomicLoad(Index, func.resolvedErrorSetPtr(@constCast(ip)), .unordered);
         }
+
+        pub fn setResolvedErrorSet(func: Func, ip: *InternPool, ies: Index) void {
+            const extra_mutex = &ip.getLocal(func.tid).mutate.extra.mutex;
+            extra_mutex.lock();
+            defer extra_mutex.unlock();
+
+            @atomicStore(Index, func.resolvedErrorSetPtr(ip), ies, .release);
+        }
     };
 
     pub const Int = struct {
src/Sema.zig
@@ -32527,6 +32527,7 @@ fn analyzeIsNonErrComptimeOnly(
                 // If the error set is empty, we must return a comptime true or false.
                 // However we want to avoid unnecessarily resolving an inferred error set
                 // in case it is already non-empty.
+                try mod.maybeUnresolveIes(func_index);
                 switch (ip.funcIesResolvedUnordered(func_index)) {
                     .anyerror_type => break :blk,
                     .none => {},
@@ -33596,6 +33597,7 @@ fn wrapErrorUnionSet(
                 .inferred_error_set_type => |func_index| ok: {
                     // We carefully do this in an order that avoids unnecessarily
                     // resolving the destination error set type.
+                    try mod.maybeUnresolveIes(func_index);
                     switch (ip.funcIesResolvedUnordered(func_index)) {
                         .anyerror_type => break :ok,
                         .none => if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, dest_err_set_ty, inst_ty, inst_src, inst_src)) {
@@ -35853,8 +35855,7 @@ fn resolveInferredErrorSet(
 
     try sema.declareDependency(.{ .interned = func_index }); // resolved IES
 
-    // TODO: during an incremental update this might not be `.none`, but the
-    // function might be out-of-date!
+    try zcu.maybeUnresolveIes(func_index);
     const resolved_ty = func.resolvedErrorSetUnordered(ip);
     if (resolved_ty != .none) return resolved_ty;
 
src/Zcu.zig
@@ -3468,3 +3468,29 @@ fn formatDependee(data: struct { dependee: InternPool.Dependee, zcu: *Zcu }, com
         },
     }
 }
+
+/// Given the `InternPool.Index` of a function, set its resolved IES to `.none` if it
+/// may be outdated. `Sema` should do this before ever loading a resolved IES.
+pub fn maybeUnresolveIes(zcu: *Zcu, func_index: InternPool.Index) !void {
+    const unit = AnalUnit.wrap(.{ .func = func_index });
+    if (zcu.outdated.contains(unit) or zcu.potentially_outdated.contains(unit)) {
+        // We're consulting the resolved IES now, but the function is outdated, so its
+        // IES may have changed. We have to assume the IES is outdated and set the resolved
+        // set back to `.none`.
+        //
+        // This will cause `PerThread.analyzeFnBody` to mark the IES as outdated when it's
+        // eventually hit.
+        //
+        // Since the IES needs to be resolved, the function body will now definitely need
+        // re-analysis (even if the IES turns out to be the same!), so mark it as
+        // definitely-outdated if it's only PO.
+        if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| {
+            const gpa = zcu.gpa;
+            try zcu.outdated.putNoClobber(gpa, unit, kv.value);
+            if (kv.value == 0) {
+                try zcu.outdated_ready.put(gpa, unit, {});
+            }
+        }
+        zcu.intern_pool.funcSetIesResolved(func_index, .none);
+    }
+}