From 804c284d2a4ece85aec64d25c241a48b4e8b11ad Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Sat, 1 Aug 2026 09:32:18 +0100 Subject: [PATCH] incremental: fix incorrect dependency in generic instances When analyzing the body of a generic function instance, we were making a dependency on the owner NAV of the *instance*, rather than that of our generic owner. Aside from being nonsensical (because the instance's owner NAV does not undergo semantic analysis), this meant that if the generic owner's owner NAV had a compile error (due to e.g. an error in the function signature), then analysis of the generic instance's body would fail, but it would not register a dependency on that NAV's value, so would not be re-analyzed if the NAV suceeded in a future update. Based on descriptions of when people had been hitting the dreaded "referenced transitive analysis errors, but none actually emitted" error, I *think* this was by far the most serious remaining incremental compilation bug in the frontend---every description I've had of such a crash occurring seems to more-or-less line up with this bug. So fingers crossed this is another big jump in incremental compilation stability! --- src/Zcu/PerThread.zig | 4 +- ...porary_analysis_error_in_generic_signature | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 test/incremental/temporary_analysis_error_in_generic_signature diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index d062f68ec946d85481b9a29c5c2eb5de5cf6e5ca..4092f27b8b7ca16a56b412805779584b801561f9 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -3262,9 +3262,7 @@ fn analyzeFuncBodyInner( defer sema.deinit(); // Every runtime function has a dependency on the source of the Decl it originates from. - // It also depends on the value of its owner Decl. try sema.declareDependency(.{ .src_hash = decl_analysis.zir_index }); - try sema.declareDependency(.{ .nav_val = func.owner_nav }); // Make sure that the declaration `Nav` still refers to this function (or its generic owner). // This will not be the case if the incremental update has changed a function type or turned a @@ -3275,6 +3273,7 @@ fn analyzeFuncBodyInner( // If we *are* still owned by the right NAV, this analysis updates `zir_body_inst` if necessary. if (func.generic_owner == .none) { + try sema.declareDependency(.{ .nav_val = func.owner_nav }); pt.ensureNavValUpToDate(func.owner_nav, reason) catch |err| switch (err) { error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .nav_val = func.owner_nav }) }), else => |e| return e, @@ -3284,6 +3283,7 @@ fn analyzeFuncBodyInner( } } else { const go_nav = zcu.funcInfo(func.generic_owner).owner_nav; + try sema.declareDependency(.{ .nav_val = go_nav }); pt.ensureNavValUpToDate(go_nav, reason) catch |err| switch (err) { error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .nav_val = go_nav }) }), else => |e| return e, diff --git a/test/incremental/temporary_analysis_error_in_generic_signature b/test/incremental/temporary_analysis_error_in_generic_signature new file mode 100644 index 0000000000000000000000000000000000000000..d8db72050b3af1164a312e1e7a1ed1f89bfc3dae --- /dev/null +++ b/test/incremental/temporary_analysis_error_in_generic_signature @@ -0,0 +1,46 @@ +#update=initial version +#file=main.zig +//! The original repro here depends on re-analysis order, which depends on +//! declaration order, so this exact declaration order must be used. +const Foo = struct { x: u8 }; +pub fn main(init: std.process.Init) !void { + const c = bar('Z').x; + try std.Io.File.stdout().writeStreamingAll(init.io, &.{ c, '\n' }); +} +fn bar(comptime x: u8) @This().Foo { + return .{ .x = x }; +} +const std = @import("std"); +#expect_stdout="Z\n" + +#update=change generic signature to use non-existent member +#file=main.zig +//! The original repro here depends on re-analysis order, which depends on +//! declaration order, so this exact declaration order must be used. +const Foo = struct { x: u8 }; +pub fn main(init: std.process.Init) !void { + const c = bar('Z').x; + try std.Io.File.stdout().writeStreamingAll(init.io, &.{ c, '\n' }); +} +fn bar(comptime x: u8) @This().FooAlias { + return .{ .x = x }; +} +const std = @import("std"); +#expect_error=main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias' +#expect_error=main.zig:1:1: note: struct declared here + +#update=add that member, fixing the error +#file=main.zig +//! The original repro here depends on re-analysis order, which depends on +//! declaration order, so this exact declaration order must be used. +const Foo = struct { x: u8 }; +const FooAlias = Foo; +pub fn main(init: std.process.Init) !void { + const c = bar('Z').x; + try std.Io.File.stdout().writeStreamingAll(init.io, &.{ c, '\n' }); +} +fn bar(comptime x: u8) @This().FooAlias { + return .{ .x = x }; +} +const std = @import("std"); +#expect_stdout="Z\n" -- 2.54.0