authorgravatar for kappaloris@gmail.comLoris Cro <kappaloris@gmail.com> 2022-03-02 18:36:44+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-19 19:10:11-07:00
log028c8a3c91286fe42d915cfe35976e3392895447
tree70e03b02ed28fd07ea7ae28c5ffa5f9fa789e96c
parent580e633777e55bcfad6f039f8acfa338d686368e

autodoc: added support for same-file lazy resolution of decl paths


1 files changed, 178 insertions(+), 70 deletions(-)

src/Autodoc.zig+178-70
...@@ -9,11 +9,24 @@ const Ref = Zir.Inst.Ref;...@@ -9,11 +9,24 @@ const Ref = Zir.Inst.Ref;
9module: *Module,9module: *Module,
10doc_location: Compilation.EmitLoc,10doc_location: Compilation.EmitLoc,
11arena: std.mem.Allocator,11arena: std.mem.Allocator,
12files: std.AutoHashMapUnmanaged(*File, DocData.AutodocFile) = .{},12files: std.AutoHashMapUnmanaged(*File, usize) = .{},
13types: std.ArrayListUnmanaged(DocData.Type) = .{},13types: std.ArrayListUnmanaged(DocData.Type) = .{},
14decls: std.ArrayListUnmanaged(DocData.Decl) = .{},14decls: std.ArrayListUnmanaged(DocData.Decl) = .{},
15ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{},15ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{},
16comptimeExprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},16comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},
17decl_paths_pending_on_decls: std.AutoHashMapUnmanaged(
18 usize,
19 std.ArrayListUnmanaged(DeclPathResumeInfo),
20) = .{},
21decl_paths_pending_on_types: std.AutoHashMapUnmanaged(
22 usize,
23 std.ArrayListUnmanaged(DeclPathResumeInfo),
24) = .{},
25
26const DeclPathResumeInfo = struct {
27 file: *File,
28 path: []usize,
29};
1730
18var arena_allocator: std.heap.ArenaAllocator = undefined;31var arena_allocator: std.heap.ArenaAllocator = undefined;
19pub fn init(m: *Module, doc_location: Compilation.EmitLoc) Autodoc {32pub fn init(m: *Module, doc_location: Compilation.EmitLoc) Autodoc {
...@@ -129,22 +142,23 @@ pub fn generateZirData(self: *Autodoc) !void {...@@ -129,22 +142,23 @@ pub fn generateZirData(self: *Autodoc) !void {
129142
130 var root_scope = Scope{ .parent = null };143 var root_scope = Scope{ .parent = null };
131 try self.ast_nodes.append(self.arena, .{ .name = "(root)" });144 try self.ast_nodes.append(self.arena, .{ .name = "(root)" });
132 try self.files.put(self.arena, file, .{145 try self.files.put(self.arena, file, self.types.items.len);
133 .analyzed = false,
134 .root_struct = self.types.items.len,
135 });
136 const main_type_index = try self.walkInstruction(file, &root_scope, Zir.main_struct_inst);146 const main_type_index = try self.walkInstruction(file, &root_scope, Zir.main_struct_inst);
137 self.files.getPtr(file).?.analyzed = true;
138147
139 // TODO: solve every single pending declpath whose analysis148 if (self.decl_paths_pending_on_decls.count() > 0) {
140 // was delayed because of circular imports.149 @panic("some decl paths were never fully analized (pending on decls)");
150 }
151
152 if (self.decl_paths_pending_on_types.count() > 0) {
153 @panic("some decl paths were never fully analized (pending on types)");
154 }
141155
142 var data = DocData{156 var data = DocData{
143 .files = .{ .data = self.files },157 .files = .{ .data = self.files },
144 .types = self.types.items,158 .types = self.types.items,
145 .decls = self.decls.items,159 .decls = self.decls.items,
146 .astNodes = self.ast_nodes.items,160 .astNodes = self.ast_nodes.items,
147 .comptimeExprs = self.comptimeExprs.items,161 .comptimeExprs = self.comptime_exprs.items,
148 };162 };
149163
150 data.packages[0].main = main_type_index.type;164 data.packages[0].main = main_type_index.type;
...@@ -232,7 +246,7 @@ const DocData = struct {...@@ -232,7 +246,7 @@ const DocData = struct {
232 astNodes: []AstNode,246 astNodes: []AstNode,
233 files: struct {247 files: struct {
234 // this struct is a temporary hack to support json serialization248 // this struct is a temporary hack to support json serialization
235 data: std.AutoHashMapUnmanaged(*File, AutodocFile),249 data: std.AutoHashMapUnmanaged(*File, usize),
236 pub fn jsonStringify(250 pub fn jsonStringify(
237 self: @This(),251 self: @This(),
238 opt: std.json.StringifyOptions,252 opt: std.json.StringifyOptions,
...@@ -248,7 +262,7 @@ const DocData = struct {...@@ -248,7 +262,7 @@ const DocData = struct {
248 if (options.whitespace) |ws| try ws.outputIndent(w);262 if (options.whitespace) |ws| try ws.outputIndent(w);
249 try w.print("\"{s}\": {d}", .{263 try w.print("\"{s}\": {d}", .{
250 kv.key_ptr.*.sub_file_path,264 kv.key_ptr.*.sub_file_path,
251 kv.value_ptr.root_struct,265 kv.value_ptr.*,
252 });266 });
253 if (idx != self.data.count() - 1) try w.writeByte(',');267 if (idx != self.data.count() - 1) try w.writeByte(',');
254 try w.writeByte('\n');268 try w.writeByte('\n');
...@@ -261,17 +275,17 @@ const DocData = struct {...@@ -261,17 +275,17 @@ const DocData = struct {
261 decls: []Decl,275 decls: []Decl,
262 comptimeExprs: []ComptimeExpr,276 comptimeExprs: []ComptimeExpr,
263277
264 const AutodocFile = struct {
265 analyzed: bool, // omitted in json data
266 root_struct: usize, // index into `types`
267 };
268
269 const DocTypeKinds = blk: {278 const DocTypeKinds = blk: {
270 var info = @typeInfo(std.builtin.TypeId);279 var info = @typeInfo(std.builtin.TypeId);
271 info.Enum.fields = info.Enum.fields ++ [1]std.builtin.TypeInfo.EnumField{280 const original_len = info.Enum.fields.len;
281 info.Enum.fields = info.Enum.fields ++ [2]std.builtin.TypeInfo.EnumField{
272 .{282 .{
273 .name = "ComptimeExpr",283 .name = "ComptimeExpr",
274 .value = info.Enum.fields.len,284 .value = original_len,
285 },
286 .{
287 .name = "Unanalyzed",
288 .value = original_len + 1,
275 },289 },
276 };290 };
277 break :blk @Type(info);291 break :blk @Type(info);
...@@ -298,6 +312,7 @@ const DocData = struct {...@@ -298,6 +312,7 @@ const DocData = struct {
298 value: WalkResult,312 value: WalkResult,
299 // The index in astNodes of the `test declname { }` node313 // The index in astNodes of the `test declname { }` node
300 decltest: ?usize = null,314 decltest: ?usize = null,
315 _analyzed: bool, // omitted in json data
301 };316 };
302317
303 const AstNode = struct {318 const AstNode = struct {
...@@ -310,6 +325,7 @@ const DocData = struct {...@@ -310,6 +325,7 @@ const DocData = struct {
310 };325 };
311326
312 const Type = union(DocTypeKinds) {327 const Type = union(DocTypeKinds) {
328 Unanalyzed: void,
313 Type: struct { name: []const u8 },329 Type: struct { name: []const u8 },
314 Void: struct { name: []const u8 },330 Void: struct { name: []const u8 },
315 Bool: struct { name: []const u8 },331 Bool: struct { name: []const u8 },
...@@ -480,7 +496,7 @@ const DocData = struct {...@@ -480,7 +496,7 @@ const DocData = struct {
480 @"struct": Struct,496 @"struct": Struct,
481 bool: bool,497 bool: bool,
482 type: usize, // index in `types`498 type: usize, // index in `types`
483 declPath: []usize, // indices in `decls`499 declPath: []usize, // indices in `decl`
484 int: struct {500 int: struct {
485 typeRef: TypeRef,501 typeRef: TypeRef,
486 value: usize, // direct value502 value: usize, // direct value
...@@ -584,16 +600,12 @@ fn walkInstruction(...@@ -584,16 +600,12 @@ fn walkInstruction(
584 // importFile cannot error out since all files600 // importFile cannot error out since all files
585 // are already loaded at this point601 // are already loaded at this point
586 const new_file = self.module.importFile(file, path) catch unreachable;602 const new_file = self.module.importFile(file, path) catch unreachable;
587
588 const result = try self.files.getOrPut(self.arena, new_file.file);603 const result = try self.files.getOrPut(self.arena, new_file.file);
589 if (result.found_existing) {604 if (result.found_existing) {
590 return DocData.WalkResult{ .type = result.value_ptr.root_struct };605 return DocData.WalkResult{ .type = result.value_ptr.* };
591 }606 }
592607
593 result.value_ptr.* = .{608 result.value_ptr.* = self.types.items.len;
594 .analyzed = false,
595 .root_struct = self.types.items.len,
596 };
597609
598 var new_scope = Scope{ .parent = null };610 var new_scope = Scope{ .parent = null };
599 const new_file_walk_result = self.walkInstruction(611 const new_file_walk_result = self.walkInstruction(
...@@ -601,14 +613,12 @@ fn walkInstruction(...@@ -601,14 +613,12 @@ fn walkInstruction(
601 &new_scope,613 &new_scope,
602 Zir.main_struct_inst,614 Zir.main_struct_inst,
603 );615 );
604 // We re-access the hashmap in case it was modified616
605 // by walkInstruction()
606 self.files.getPtr(new_file.file).?.analyzed = true;
607 return new_file_walk_result;617 return new_file_walk_result;
608 },618 },
609 .block => {619 .block => {
610 const res = DocData.WalkResult{ .comptimeExpr = self.comptimeExprs.items.len };620 const res = DocData.WalkResult{ .comptimeExpr = self.comptime_exprs.items.len };
611 try self.comptimeExprs.append(self.arena, .{621 try self.comptime_exprs.append(self.arena, .{
612 .code = "if(banana) 1 else 0",622 .code = "if(banana) 1 else 0",
613 .typeRef = .{ .type = 0 },623 .typeRef = .{ .type = 0 },
614 });624 });
...@@ -669,7 +679,7 @@ fn walkInstruction(...@@ -669,7 +679,7 @@ fn walkInstruction(
669 // TODO: Actually, this is a good moment to check if679 // TODO: Actually, this is a good moment to check if
670 // the result is indeed a type!!680 // the result is indeed a type!!
671 .comptimeExpr => {681 .comptimeExpr => {
672 self.comptimeExprs.items[operand.comptimeExpr].typeRef = dest_type_ref;682 self.comptime_exprs.items[operand.comptimeExpr].typeRef = dest_type_ref;
673 },683 },
674 .int => operand.int.typeRef = dest_type_ref,684 .int => operand.int.typeRef = dest_type_ref,
675 .@"struct" => operand.@"struct".typeRef = dest_type_ref,685 .@"struct" => operand.@"struct".typeRef = dest_type_ref,
...@@ -731,35 +741,7 @@ fn walkInstruction(...@@ -731,35 +741,7 @@ fn walkInstruction(
731 // the analyzed data corresponding to the top-most decl of this path.741 // the analyzed data corresponding to the top-most decl of this path.
732 // We are now going to reverse loop over `path` to resolve each name742 // We are now going to reverse loop over `path` to resolve each name
733 // to its corresponding index in `decls`.743 // to its corresponding index in `decls`.
734744 try self.tryResolveDeclPath(file, path.items);
735 var i: usize = path.items.len;
736 while (i > 1) {
737 i -= 1;
738 const parent = self.decls.items[path.items[i]];
739 const child_decl_name = file.zir.nullTerminatedString(path.items[i - 1]);
740 switch (parent.value) {
741 else => {
742 std.debug.print(
743 "TODO: handle `{s}`in walkInstruction.field_val\n",
744 .{@tagName(parent.value)},
745 );
746 unreachable;
747 },
748 .type => |t_index| {
749 const t_struct = self.types.items[t_index].Struct; // todo: support more types
750 for (t_struct.pubDecls) |d| {
751 // TODO: this could be improved a lot
752 // by having our own string table!
753 const decl = self.decls.items[d];
754 if (std.mem.eql(u8, decl.name, child_decl_name)) {
755 path.items[i - 1] = d;
756 continue;
757 }
758 }
759 },
760 }
761 }
762
763 return DocData.WalkResult{ .declPath = path.items };745 return DocData.WalkResult{ .declPath = path.items };
764 },746 },
765 .int_type => {747 .int_type => {
...@@ -841,6 +823,29 @@ fn walkInstruction(...@@ -841,6 +823,29 @@ fn walkInstruction(
841 return DocData.WalkResult{ .type = self.types.items.len - 1 };823 return DocData.WalkResult{ .type = self.types.items.len - 1 };
842 },824 },
843 .extended => {825 .extended => {
826 // TODO: this assumes that we always return a type when analyzing
827 // an extended instruction. Also we willingfully not reserve
828 // a slot for functions (handled right above) despite them
829 // being stored in `types`. The reason why we reserve a slot
830 // in here, is for decl paths and their resolution system.
831 const type_slot_index = self.types.items.len;
832 try self.types.append(self.arena, .{ .Unanalyzed = {} });
833
834 defer {
835 if (self.decl_paths_pending_on_types.get(type_slot_index)) |paths| {
836 for (paths.items) |resume_info| {
837 self.tryResolveDeclPath(resume_info.file, resume_info.path) catch {
838 @panic("Out of memory");
839 };
840 }
841
842 _ = self.decl_paths_pending_on_types.remove(type_slot_index);
843 // TODO: we should deallocate the arraylist that holds all the
844 // decl paths. not doing it now since it's arena-allocated
845 // anyway, but maybe we should put it elsewhere.
846 }
847 }
848
844 const extended = data[inst_index].extended;849 const extended = data[inst_index].extended;
845 switch (extended.opcode) {850 switch (extended.opcode) {
846 else => {851 else => {
...@@ -898,6 +903,9 @@ fn walkInstruction(...@@ -898,6 +903,9 @@ fn walkInstruction(
898 {903 {
899 var it = file.zir.declIterator(@intCast(u32, inst_index));904 var it = file.zir.declIterator(@intCast(u32, inst_index));
900 try self.decls.resize(self.arena, decls_first_index + it.decls_len);905 try self.decls.resize(self.arena, decls_first_index + it.decls_len);
906 for (self.decls.items[decls_first_index..]) |*slot| {
907 slot._analyzed = false;
908 }
901 var decls_slot_index = decls_first_index;909 var decls_slot_index = decls_first_index;
902 while (it.next()) |d| : (decls_slot_index += 1) {910 while (it.next()) |d| : (decls_slot_index += 1) {
903 const decl_name_index = file.zir.extra[d.sub_index + 5];911 const decl_name_index = file.zir.extra[d.sub_index + 5];
...@@ -937,7 +945,7 @@ fn walkInstruction(...@@ -937,7 +945,7 @@ fn walkInstruction(
937945
938 self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;946 self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
939947
940 try self.types.append(self.arena, .{948 self.types.items[type_slot_index] = .{
941 .Union = .{949 .Union = .{
942 .name = "todo_name",950 .name = "todo_name",
943 .src = self_ast_node_index,951 .src = self_ast_node_index,
...@@ -945,9 +953,9 @@ fn walkInstruction(...@@ -945,9 +953,9 @@ fn walkInstruction(
945 .pubDecls = decl_indexes.items,953 .pubDecls = decl_indexes.items,
946 .fields = field_type_refs.items,954 .fields = field_type_refs.items,
947 },955 },
948 });956 };
949957
950 return DocData.WalkResult{ .type = self.types.items.len - 1 };958 return DocData.WalkResult{ .type = type_slot_index };
951 },959 },
952 .enum_decl => {960 .enum_decl => {
953 var scope: Scope = .{ .parent = parent_scope };961 var scope: Scope = .{ .parent = parent_scope };
...@@ -998,6 +1006,9 @@ fn walkInstruction(...@@ -998,6 +1006,9 @@ fn walkInstruction(
998 {1006 {
999 var it = file.zir.declIterator(@intCast(u32, inst_index));1007 var it = file.zir.declIterator(@intCast(u32, inst_index));
1000 try self.decls.resize(self.arena, decls_first_index + it.decls_len);1008 try self.decls.resize(self.arena, decls_first_index + it.decls_len);
1009 for (self.decls.items[decls_first_index..]) |*slot| {
1010 slot._analyzed = false;
1011 }
1001 var decls_slot_index = decls_first_index;1012 var decls_slot_index = decls_first_index;
1002 while (it.next()) |d| : (decls_slot_index += 1) {1013 while (it.next()) |d| : (decls_slot_index += 1) {
1003 const decl_name_index = file.zir.extra[d.sub_index + 5];1014 const decl_name_index = file.zir.extra[d.sub_index + 5];
...@@ -1063,16 +1074,16 @@ fn walkInstruction(...@@ -1063,16 +1074,16 @@ fn walkInstruction(
10631074
1064 self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;1075 self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
10651076
1066 try self.types.append(self.arena, .{1077 self.types.items[type_slot_index] = .{
1067 .Enum = .{1078 .Enum = .{
1068 .name = "todo_name",1079 .name = "todo_name",
1069 .src = self_ast_node_index,1080 .src = self_ast_node_index,
1070 .privDecls = priv_decl_indexes.items,1081 .privDecls = priv_decl_indexes.items,
1071 .pubDecls = decl_indexes.items,1082 .pubDecls = decl_indexes.items,
1072 },1083 },
1073 });1084 };
10741085
1075 return DocData.WalkResult{ .type = self.types.items.len - 1 };1086 return DocData.WalkResult{ .type = type_slot_index };
1076 },1087 },
1077 .struct_decl => {1088 .struct_decl => {
1078 var scope: Scope = .{ .parent = parent_scope };1089 var scope: Scope = .{ .parent = parent_scope };
...@@ -1116,6 +1127,9 @@ fn walkInstruction(...@@ -1116,6 +1127,9 @@ fn walkInstruction(
1116 {1127 {
1117 var it = file.zir.declIterator(@intCast(u32, inst_index));1128 var it = file.zir.declIterator(@intCast(u32, inst_index));
1118 try self.decls.resize(self.arena, decls_first_index + it.decls_len);1129 try self.decls.resize(self.arena, decls_first_index + it.decls_len);
1130 for (self.decls.items[decls_first_index..]) |*slot| {
1131 slot._analyzed = false;
1132 }
1119 var decls_slot_index = decls_first_index;1133 var decls_slot_index = decls_first_index;
1120 while (it.next()) |d| : (decls_slot_index += 1) {1134 while (it.next()) |d| : (decls_slot_index += 1) {
1121 const decl_name_index = file.zir.extra[d.sub_index + 5];1135 const decl_name_index = file.zir.extra[d.sub_index + 5];
...@@ -1149,7 +1163,7 @@ fn walkInstruction(...@@ -1149,7 +1163,7 @@ fn walkInstruction(
11491163
1150 self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;1164 self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
11511165
1152 try self.types.append(self.arena, .{1166 self.types.items[type_slot_index] = .{
1153 .Struct = .{1167 .Struct = .{
1154 .name = "todo_name",1168 .name = "todo_name",
1155 .src = self_ast_node_index,1169 .src = self_ast_node_index,
...@@ -1157,9 +1171,9 @@ fn walkInstruction(...@@ -1157,9 +1171,9 @@ fn walkInstruction(
1157 .pubDecls = decl_indexes.items,1171 .pubDecls = decl_indexes.items,
1158 .fields = field_type_refs.items,1172 .fields = field_type_refs.items,
1159 },1173 },
1160 });1174 };
11611175
1162 return DocData.WalkResult{ .type = self.types.items.len - 1 };1176 return DocData.WalkResult{ .type = type_slot_index };
1163 },1177 },
1164 }1178 }
1165 },1179 },
...@@ -1310,6 +1324,7 @@ fn walkDecls(...@@ -1310,6 +1324,7 @@ fn walkDecls(
1310 };1324 };
1311 self.decls.items[decl_being_tested].decltest = ast_node_index;1325 self.decls.items[decl_being_tested].decltest = ast_node_index;
1312 self.decls.items[decls_slot_index] = .{1326 self.decls.items[decls_slot_index] = .{
1327 ._analyzed = true,
1313 .name = "test",1328 .name = "test",
1314 .src = ast_node_index,1329 .src = ast_node_index,
1315 .value = .{ .type = 0 },1330 .value = .{ .type = 0 },
...@@ -1368,17 +1383,110 @@ fn walkDecls(...@@ -1368,17 +1383,110 @@ fn walkDecls(
1368 // };1383 // };
13691384
1370 self.decls.items[decls_slot_index] = .{1385 self.decls.items[decls_slot_index] = .{
1386 ._analyzed = true,
1371 .name = name,1387 .name = name,
1372 .src = ast_node_index,1388 .src = ast_node_index,
1373 // .typeRef = decl_type_ref,1389 // .typeRef = decl_type_ref,
1374 .value = walk_result,1390 .value = walk_result,
1375 .kind = "const", // find where this information can be found1391 .kind = "const", // find where this information can be found
1376 };1392 };
1393
1394 // Unblock any pending decl path that was waiting for this decl.
1395 if (self.decl_paths_pending_on_decls.get(decls_slot_index)) |paths| {
1396 for (paths.items) |resume_info| {
1397 try self.tryResolveDeclPath(resume_info.file, resume_info.path);
1398 }
1399
1400 _ = self.decl_paths_pending_on_decls.remove(decls_slot_index);
1401 // TODO: we should deallocate the arraylist that holds all the
1402 // decl paths. not doing it now since it's arena-allocated
1403 // anyway, but maybe we should put it elsewhere.
1404 }
1377 }1405 }
13781406
1379 return extra_index;1407 return extra_index;
1380}1408}
13811409
1410/// An unresolved path has a decl index at its end, while every other element
1411/// is an index into the string table. Resolving means resolving iteratively
1412/// each string into a decl_index. If we encounter an unanalyzed decl during
1413/// the process, we append the unsolved sub-path to `self.decl_paths_pending_on_decls`
1414/// and bail out.
1415fn tryResolveDeclPath(
1416 self: *Autodoc,
1417 /// File from which the decl path originates.
1418 file: *File,
1419 path: []usize,
1420) !void {
1421 var i: usize = path.len;
1422 while (i > 1) {
1423 i -= 1;
1424 const decl_index = path[i];
1425 const string_index = path[i - 1];
1426
1427 const parent = self.decls.items[decl_index];
1428 if (!parent._analyzed) {
1429 const res = try self.decl_paths_pending_on_decls.getOrPut(self.arena, decl_index);
1430 if (!res.found_existing) res.value_ptr.* = .{};
1431 try res.value_ptr.*.append(self.arena, .{
1432 .file = file,
1433 .path = path[0 .. i + 1],
1434 });
1435 return;
1436 }
1437
1438 const child_decl_name = file.zir.nullTerminatedString(string_index);
1439 switch (parent.value) {
1440 else => {
1441 std.debug.panic(
1442 "TODO: handle `{s}`in walkInstruction.field_val\n",
1443 .{@tagName(parent.value)},
1444 );
1445 },
1446 .type => |t_index| switch (self.types.items[t_index]) {
1447 else => {
1448 std.debug.panic(
1449 "TODO: handle `{s}` in tryResolveDeclPath.type\n",
1450 .{@tagName(self.types.items[t_index])},
1451 );
1452 },
1453 .Unanalyzed => {
1454 const res = try self.decl_paths_pending_on_types.getOrPut(
1455 self.arena,
1456 t_index,
1457 );
1458 if (!res.found_existing) res.value_ptr.* = .{};
1459 try res.value_ptr.*.append(self.arena, .{
1460 .file = file,
1461 .path = path[0 .. i + 1],
1462 });
1463 return;
1464 },
1465 .Struct => |t_struct| {
1466 for (t_struct.pubDecls) |d| {
1467 // TODO: this could be improved a lot
1468 // by having our own string table!
1469 const decl = self.decls.items[d];
1470 if (std.mem.eql(u8, decl.name, child_decl_name)) {
1471 path[i - 1] = d;
1472 continue;
1473 }
1474 }
1475 for (t_struct.privDecls) |d| {
1476 // TODO: this could be improved a lot
1477 // by having our own string table!
1478 const decl = self.decls.items[d];
1479 if (std.mem.eql(u8, decl.name, child_decl_name)) {
1480 path[i - 1] = d;
1481 continue;
1482 }
1483 }
1484 },
1485 },
1486 }
1487 }
1488}
1489
1382fn collectUnionFieldInfo(1490fn collectUnionFieldInfo(
1383 self: *Autodoc,1491 self: *Autodoc,
1384 file: *File,1492 file: *File,