authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-01 09:32:18+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-01 11:27:38+01:00
log804c284d2a4ece85aec64d25c241a48b4e8b11ad
tree401d9620e5b1a798cbabf3ea7ebfdb75524a3219
parentf134f4345cf8484b82c46278074eb45af0efaf2e
signaturelock-open Commit is signed but in an unrecognized format.

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!

2 files changed, 48 insertions(+), 2 deletions(-)

src/Zcu/PerThread.zig+2-2
......@@ -3262,9 +3262,7 @@ fn analyzeFuncBodyInner(
32623262 defer sema.deinit();
32633263
32643264 // Every runtime function has a dependency on the source of the Decl it originates from.
3265 // It also depends on the value of its owner Decl.
32663265 try sema.declareDependency(.{ .src_hash = decl_analysis.zir_index });
3267 try sema.declareDependency(.{ .nav_val = func.owner_nav });
32683266
32693267 // Make sure that the declaration `Nav` still refers to this function (or its generic owner).
32703268 // This will not be the case if the incremental update has changed a function type or turned a
......@@ -3275,6 +3273,7 @@ fn analyzeFuncBodyInner(
32753273 // If we *are* still owned by the right NAV, this analysis updates `zir_body_inst` if necessary.
32763274
32773275 if (func.generic_owner == .none) {
3276 try sema.declareDependency(.{ .nav_val = func.owner_nav });
32783277 pt.ensureNavValUpToDate(func.owner_nav, reason) catch |err| switch (err) {
32793278 error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .nav_val = func.owner_nav }) }),
32803279 else => |e| return e,
......@@ -3284,6 +3283,7 @@ fn analyzeFuncBodyInner(
32843283 }
32853284 } else {
32863285 const go_nav = zcu.funcInfo(func.generic_owner).owner_nav;
3286 try sema.declareDependency(.{ .nav_val = go_nav });
32873287 pt.ensureNavValUpToDate(go_nav, reason) catch |err| switch (err) {
32883288 error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .nav_val = go_nav }) }),
32893289 else => |e| return e,
test/incremental/temporary_analysis_error_in_generic_signature created+46
......@@ -0,0 +1,46 @@
1#update=initial version
2#file=main.zig
3//! The original repro here depends on re-analysis order, which depends on
4//! declaration order, so this exact declaration order must be used.
5const Foo = struct { x: u8 };
6pub fn main(init: std.process.Init) !void {
7 const c = bar('Z').x;
8 try std.Io.File.stdout().writeStreamingAll(init.io, &.{ c, '\n' });
9}
10fn bar(comptime x: u8) @This().Foo {
11 return .{ .x = x };
12}
13const std = @import("std");
14#expect_stdout="Z\n"
15
16#update=change generic signature to use non-existent member
17#file=main.zig
18//! The original repro here depends on re-analysis order, which depends on
19//! declaration order, so this exact declaration order must be used.
20const Foo = struct { x: u8 };
21pub fn main(init: std.process.Init) !void {
22 const c = bar('Z').x;
23 try std.Io.File.stdout().writeStreamingAll(init.io, &.{ c, '\n' });
24}
25fn bar(comptime x: u8) @This().FooAlias {
26 return .{ .x = x };
27}
28const std = @import("std");
29#expect_error=main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias'
30#expect_error=main.zig:1:1: note: struct declared here
31
32#update=add that member, fixing the error
33#file=main.zig
34//! The original repro here depends on re-analysis order, which depends on
35//! declaration order, so this exact declaration order must be used.
36const Foo = struct { x: u8 };
37const FooAlias = Foo;
38pub fn main(init: std.process.Init) !void {
39 const c = bar('Z').x;
40 try std.Io.File.stdout().writeStreamingAll(init.io, &.{ c, '\n' });
41}
42fn bar(comptime x: u8) @This().FooAlias {
43 return .{ .x = x };
44}
45const std = @import("std");
46#expect_stdout="Z\n"