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 {
36493649 type_layout_deps_len: u32,
36503650 struct_defaults_deps_len: u32,
36513651 func_ies_deps_len: u32,
3652 zon_file_deps_len: u32,
3652 source_file_deps_len: u32,
36533653 embed_file_deps_len: u32,
36543654 namespace_deps_len: u32,
36553655 namespace_name_deps_len: u32,
......@@ -3699,7 +3699,7 @@ pub fn saveState(comp: *Compilation) !void {
36993699 .type_layout_deps_len = @intCast(ip.type_layout_deps.count()),
37003700 .struct_defaults_deps_len = @intCast(ip.struct_defaults_deps.count()),
37013701 .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()),
37033703 .embed_file_deps_len = @intCast(ip.embed_file_deps.count()),
37043704 .namespace_deps_len = @intCast(ip.namespace_deps.count()),
37053705 .namespace_name_deps_len = @intCast(ip.namespace_name_deps.count()),
......@@ -3738,8 +3738,8 @@ pub fn saveState(comp: *Compilation) !void {
37383738 addBuf(&bufs, @ptrCast(ip.struct_defaults_deps.values()));
37393739 addBuf(&bufs, @ptrCast(ip.func_ies_deps.keys()));
37403740 addBuf(&bufs, @ptrCast(ip.func_ies_deps.values()));
3741 addBuf(&bufs, @ptrCast(ip.zon_file_deps.keys()));
3742 addBuf(&bufs, @ptrCast(ip.zon_file_deps.values()));
3741 addBuf(&bufs, @ptrCast(ip.source_file_deps.keys()));
3742 addBuf(&bufs, @ptrCast(ip.source_file_deps.values()));
37433743 addBuf(&bufs, @ptrCast(ip.embed_file_deps.keys()));
37443744 addBuf(&bufs, @ptrCast(ip.embed_file_deps.values()));
37453745 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
305305 for (unit_info.deps.items, 0..) |dependee, i| {
306306 try w.print("[{d}] ", .{i});
307307 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)}),
309309 .nav_val, .nav_ty => |nav| try w.print("{t} {d}", .{ dependee, @intFromEnum(nav) }),
310310 .type_layout, .struct_defaults, .func_ies => |ip_index| try w.print("{t} {d}", .{ dependee, @intFromEnum(ip_index) }),
311311 .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),
5757/// Dependencies on the resolved default field values of a `struct` type.
5858/// Value is index into `dep_entries` of the first dependency on this type's inits.
5959struct_defaults_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index),
60/// Dependencies on a ZON file. Triggered by `@import` of ZON.
61/// Value is index into `dep_entries` of the first dependency on this ZON file.
62zon_file_deps: std.AutoArrayHashMapUnmanaged(FileIndex, DepEntry.Index),
60/// Dependencies on a Zig or ZON source file. Triggered by `@import`.
61/// * For ZON source files, the dependency is invalidated if the file changes at all. The `@import`
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),
6368/// Dependencies on an embedded file.
6469/// Introduced by `@embedFile`; invalidated when the file changes.
6570/// Value is index into `dep_entries` of the first dependency on this `Zcu.EmbedFile`.
......@@ -112,7 +117,7 @@ pub const empty: InternPool = .{
112117 .func_ies_deps = .empty,
113118 .type_layout_deps = .empty,
114119 .struct_defaults_deps = .empty,
115 .zon_file_deps = .empty,
120 .source_file_deps = .empty,
116121 .embed_file_deps = .empty,
117122 .namespace_deps = .empty,
118123 .namespace_name_deps = .empty,
......@@ -859,7 +864,7 @@ pub const Dependee = union(enum) {
859864 func_ies: Index,
860865 type_layout: Index,
861866 struct_defaults: Index,
862 zon_file: FileIndex,
867 source_file: FileIndex,
863868 embed_file: Zcu.EmbedFile.Index,
864869 namespace: TrackedInst.Index,
865870 namespace_name: NamespaceNameKey,
......@@ -913,7 +918,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI
913918 .func_ies => |x| ip.func_ies_deps.get(x),
914919 .type_layout => |x| ip.type_layout_deps.get(x),
915920 .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),
917922 .embed_file => |x| ip.embed_file_deps.get(x),
918923 .namespace => |x| ip.namespace_deps.get(x),
919924 .namespace_name => |x| ip.namespace_name_deps.get(x),
......@@ -988,7 +993,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend
988993 .func_ies => ip.func_ies_deps,
989994 .type_layout => ip.type_layout_deps,
990995 .struct_defaults => ip.struct_defaults_deps,
991 .zon_file => ip.zon_file_deps,
996 .source_file => ip.source_file_deps,
992997 .embed_file => ip.embed_file_deps,
993998 .namespace => ip.namespace_deps,
994999 .namespace_name => ip.namespace_name_deps,
......@@ -6477,7 +6482,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator, io: Io) void {
64776482 ip.func_ies_deps.deinit(gpa);
64786483 ip.type_layout_deps.deinit(gpa);
64796484 ip.struct_defaults_deps.deinit(gpa);
6480 ip.zon_file_deps.deinit(gpa);
6485 ip.source_file_deps.deinit(gpa);
64816486 ip.embed_file_deps.deinit(gpa);
64826487 ip.namespace_deps.deinit(gpa);
64836488 ip.namespace_name_deps.deinit(gpa);
......@@ -10643,7 +10648,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
1064310648 const func_ies_deps_len = ip.func_ies_deps.count();
1064410649 const type_layout_deps_len = ip.type_layout_deps.count();
1064510650 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();
1064710652 const embed_file_deps_len = ip.embed_file_deps.count();
1064810653 const namespace_deps_len = ip.namespace_deps.count();
1064910654 const namespace_name_deps_len = ip.namespace_name_deps.count();
......@@ -10654,7 +10659,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
1065410659 const func_ies_deps_size = func_ies_deps_len * 8;
1065510660 const type_layout_deps_size = type_layout_deps_len * 8;
1065610661 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;
1065810663 const embed_file_deps_size = embed_file_deps_len * 8;
1065910664 const namespace_deps_size = namespace_deps_len * 8;
1066010665 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 {
1066810673 \\ {d} func_ies: {d} bytes
1066910674 \\ {d} type_layout: {d} bytes
1067010675 \\ {d} struct_defaults: {d} bytes
10671 \\ {d} zon_file: {d} bytes
10676 \\ {d} source_file: {d} bytes
1067210677 \\ {d} embed_file: {d} bytes
1067310678 \\ {d} namespace: {d} bytes
1067410679 \\ {d} namespace_name: {d} bytes
1067510680 \\
1067610681 , .{
1067710682 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 +
1067910684 embed_file_deps_size + namespace_deps_size + namespace_name_deps_size,
1068010685 dep_entries_len,
1068110686 dep_entries_size,
......@@ -10691,8 +10696,8 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
1069110696 type_layout_deps_size,
1069210697 struct_defaults_deps_len,
1069310698 struct_defaults_deps_size,
10694 zon_file_deps_len,
10695 zon_file_deps_size,
10699 source_file_deps_len,
10700 source_file_deps_size,
1069610701 embed_file_deps_len,
1069710702 embed_file_deps_size,
1069810703 namespace_deps_len,
src/Sema.zig+2-2
......@@ -13011,6 +13011,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
1301113011 };
1301213012 const file_index = result.file;
1301313013 const file = zcu.fileByIndex(file_index);
13014 try sema.declareDependency(.{ .source_file = file_index });
1301413015 switch (file.getMode()) {
1301513016 .zig => {
1301613017 try pt.ensureFilePopulated(file_index);
......@@ -13028,8 +13029,6 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
1302813029 if (res_ty.isGenericPoison()) break :b .none;
1302913030 break :b res_ty.toIntern();
1303013031 };
13031
13032 try sema.declareDependency(.{ .zon_file = file_index });
1303313032 const interned = try LowerZon.run(
1303413033 sema,
1303513034 file,
......@@ -34084,6 +34083,7 @@ pub fn analyzeMemoizedState(sema: *Sema, stage: InternPool.MemoizedStateStage) C
3408434083 // Get the main struct type of the root source file of `std`. No need for a reference entry
3408534084 // because `std` is always an analysis root.
3408634085 const std_file_index = zcu.module_roots.get(zcu.std_mod).?.unwrap().?;
34086 try sema.declareDependency(.{ .source_file = std_file_index });
3408734087 try pt.ensureFilePopulated(std_file_index);
3408834088 const std_type: Type = .fromInterned(zcu.fileRootType(std_file_index));
3408934089 break :block .{
src/Zcu.zig+3-3
......@@ -1014,7 +1014,7 @@ pub const File = struct {
10141014 /// changed -- this field is just a simple boolean.
10151015 ///
10161016 /// 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`.
10181018 zoir_invalidated: bool,
10191019
10201020 pub const Path = struct {
......@@ -4496,9 +4496,9 @@ fn formatDependee(data: FormatDependee, writer: *Io.Writer) Io.Writer.Error!void
44964496 const fqn = ip.getNav(ip.indexToKey(ip_index).func.owner_nav).fqn;
44974497 return writer.print("func_ies('{f}')", .{fqn.fmt(ip)});
44984498 },
4499 .zon_file => |file| {
4499 .source_file => |file| {
45004500 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)});
45024502 },
45034503 .embed_file => |ef_idx| {
45044504 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 {
838838 .zig => {}, // logic below
839839 .zon => {
840840 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 });
842842 file.zoir_invalidated = false;
843843 }
844844 continue;
......@@ -988,8 +988,8 @@ fn updateZirRefs(pt: Zcu.PerThread) (Io.Cancelable || Allocator.Error)!void {
988988 // be re-analyzed (causing the struct's namespace to be re-scanned). It's fine to do this
989989 // now because this work is fast (no actual Sema work is happening, we're just updating the
990990 // namespace contents). We must do this after updating ZIR refs above, since `scanNamespace`
991 // will track some instructions.
992 try pt.updateFileNamespace(file_index);
991 // calls will track some instructions.
992 try pt.updateFileRootStructType(file_index);
993993 }
994994}
995995
......@@ -2350,27 +2350,49 @@ fn analyzeFuncBody(
23502350 return .{ .ies_outdated = ies_outdated };
23512351}
23522352
2353/// Re-scan the namespace of a file's root struct type on an incremental update.
2354/// The file must have successfully populated ZIR.
2355/// If the file's root struct type is not populated (the file is unreferenced), nothing is done.
2356/// This is called by `updateZirRefs` for all updated files before the main work loop.
2357/// This function does not perform any semantic analysis.
2358fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void {
2353/// The given file has been modified on this incremental update, so if it has a populated root
2354/// struct type, either re-scan its namespace, or clear it and invalidate dependencies if the
2355/// type is no longer valid. See comments in body for more details.
2356///
2357/// Called by `updateZirRefs` for all updated Zig source files before the main update loop.
2358///
2359/// Asserts that the file has successfully populated ZIR.
2360fn updateFileRootStructType(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void {
23592361 const zcu = pt.zcu;
2362 const ip = &zcu.intern_pool;
23602363
23612364 const file = zcu.fileByIndex(file_index);
23622365 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}", .{
23662376 file.mod.?.fully_qualified_name,
23672377 file.sub_file_path,
23682378 });
23692379
2370 const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu);
2371 const decls = file.zir.?.getStructDecl(.main_struct_inst).decls;
2372 try pt.scanNamespace(namespace_index, decls);
2373 zcu.namespacePtr(namespace_index).generation = zcu.generation;
2380 if (loaded_struct.zir_index.resolve(ip) == null) {
2381 // The file's root struct decl has been lost, so a new struct type must be interned at a new
2382 // `InternPool.Index`. Clear the file's root type so that `ensureFilePopulated` will do that
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 }
23742396}
23752397
23762398/// 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"