| ... | ... | @@ -9,11 +9,24 @@ const Ref = Zir.Inst.Ref; |
| 9 | 9 | module: *Module, |
| 10 | 10 | doc_location: Compilation.EmitLoc, |
| 11 | 11 | arena: std.mem.Allocator, |
| 12 | | files: std.AutoHashMapUnmanaged(*File, DocData.AutodocFile) = .{}, |
| 12 | files: std.AutoHashMapUnmanaged(*File, usize) = .{}, |
| 13 | 13 | types: std.ArrayListUnmanaged(DocData.Type) = .{}, |
| 14 | 14 | decls: std.ArrayListUnmanaged(DocData.Decl) = .{}, |
| 15 | 15 | ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{}, |
| 16 | | comptimeExprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{}, |
| 16 | comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{}, |
| 17 | decl_paths_pending_on_decls: std.AutoHashMapUnmanaged( |
| 18 | usize, |
| 19 | std.ArrayListUnmanaged(DeclPathResumeInfo), |
| 20 | ) = .{}, |
| 21 | decl_paths_pending_on_types: std.AutoHashMapUnmanaged( |
| 22 | usize, |
| 23 | std.ArrayListUnmanaged(DeclPathResumeInfo), |
| 24 | ) = .{}, |
| 25 | |
| 26 | const DeclPathResumeInfo = struct { |
| 27 | file: *File, |
| 28 | path: []usize, |
| 29 | }; |
| 17 | 30 | |
| 18 | 31 | var arena_allocator: std.heap.ArenaAllocator = undefined; |
| 19 | 32 | pub fn init(m: *Module, doc_location: Compilation.EmitLoc) Autodoc { |
| ... | ... | @@ -129,22 +142,23 @@ pub fn generateZirData(self: *Autodoc) !void { |
| 129 | 142 | |
| 130 | 143 | var root_scope = Scope{ .parent = null }; |
| 131 | 144 | try self.ast_nodes.append(self.arena, .{ .name = "(root)" }); |
| 132 | | try self.files.put(self.arena, file, .{ |
| 133 | | .analyzed = false, |
| 134 | | .root_struct = self.types.items.len, |
| 135 | | }); |
| 145 | try self.files.put(self.arena, file, self.types.items.len); |
| 136 | 146 | const main_type_index = try self.walkInstruction(file, &root_scope, Zir.main_struct_inst); |
| 137 | | self.files.getPtr(file).?.analyzed = true; |
| 138 | 147 | |
| 139 | | // TODO: solve every single pending declpath whose analysis |
| 140 | | // was delayed because of circular imports. |
| 148 | if (self.decl_paths_pending_on_decls.count() > 0) { |
| 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 | } |
| 141 | 155 | |
| 142 | 156 | var data = DocData{ |
| 143 | 157 | .files = .{ .data = self.files }, |
| 144 | 158 | .types = self.types.items, |
| 145 | 159 | .decls = self.decls.items, |
| 146 | 160 | .astNodes = self.ast_nodes.items, |
| 147 | | .comptimeExprs = self.comptimeExprs.items, |
| 161 | .comptimeExprs = self.comptime_exprs.items, |
| 148 | 162 | }; |
| 149 | 163 | |
| 150 | 164 | data.packages[0].main = main_type_index.type; |
| ... | ... | @@ -232,7 +246,7 @@ const DocData = struct { |
| 232 | 246 | astNodes: []AstNode, |
| 233 | 247 | files: struct { |
| 234 | 248 | // this struct is a temporary hack to support json serialization |
| 235 | | data: std.AutoHashMapUnmanaged(*File, AutodocFile), |
| 249 | data: std.AutoHashMapUnmanaged(*File, usize), |
| 236 | 250 | pub fn jsonStringify( |
| 237 | 251 | self: @This(), |
| 238 | 252 | opt: std.json.StringifyOptions, |
| ... | ... | @@ -248,7 +262,7 @@ const DocData = struct { |
| 248 | 262 | if (options.whitespace) |ws| try ws.outputIndent(w); |
| 249 | 263 | try w.print("\"{s}\": {d}", .{ |
| 250 | 264 | kv.key_ptr.*.sub_file_path, |
| 251 | | kv.value_ptr.root_struct, |
| 265 | kv.value_ptr.*, |
| 252 | 266 | }); |
| 253 | 267 | if (idx != self.data.count() - 1) try w.writeByte(','); |
| 254 | 268 | try w.writeByte('\n'); |
| ... | ... | @@ -261,17 +275,17 @@ const DocData = struct { |
| 261 | 275 | decls: []Decl, |
| 262 | 276 | comptimeExprs: []ComptimeExpr, |
| 263 | 277 | |
| 264 | | const AutodocFile = struct { |
| 265 | | analyzed: bool, // omitted in json data |
| 266 | | root_struct: usize, // index into `types` |
| 267 | | }; |
| 268 | | |
| 269 | 278 | const DocTypeKinds = blk: { |
| 270 | 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 | 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 | 291 | break :blk @Type(info); |
| ... | ... | @@ -298,6 +312,7 @@ const DocData = struct { |
| 298 | 312 | value: WalkResult, |
| 299 | 313 | // The index in astNodes of the `test declname { }` node |
| 300 | 314 | decltest: ?usize = null, |
| 315 | _analyzed: bool, // omitted in json data |
| 301 | 316 | }; |
| 302 | 317 | |
| 303 | 318 | const AstNode = struct { |
| ... | ... | @@ -310,6 +325,7 @@ const DocData = struct { |
| 310 | 325 | }; |
| 311 | 326 | |
| 312 | 327 | const Type = union(DocTypeKinds) { |
| 328 | Unanalyzed: void, |
| 313 | 329 | Type: struct { name: []const u8 }, |
| 314 | 330 | Void: struct { name: []const u8 }, |
| 315 | 331 | Bool: struct { name: []const u8 }, |
| ... | ... | @@ -480,7 +496,7 @@ const DocData = struct { |
| 480 | 496 | @"struct": Struct, |
| 481 | 497 | bool: bool, |
| 482 | 498 | type: usize, // index in `types` |
| 483 | | declPath: []usize, // indices in `decls` |
| 499 | declPath: []usize, // indices in `decl` |
| 484 | 500 | int: struct { |
| 485 | 501 | typeRef: TypeRef, |
| 486 | 502 | value: usize, // direct value |
| ... | ... | @@ -584,16 +600,12 @@ fn walkInstruction( |
| 584 | 600 | // importFile cannot error out since all files |
| 585 | 601 | // are already loaded at this point |
| 586 | 602 | const new_file = self.module.importFile(file, path) catch unreachable; |
| 587 | | |
| 588 | 603 | const result = try self.files.getOrPut(self.arena, new_file.file); |
| 589 | 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 | } |
| 592 | 607 | |
| 593 | | result.value_ptr.* = .{ |
| 594 | | .analyzed = false, |
| 595 | | .root_struct = self.types.items.len, |
| 596 | | }; |
| 608 | result.value_ptr.* = self.types.items.len; |
| 597 | 609 | |
| 598 | 610 | var new_scope = Scope{ .parent = null }; |
| 599 | 611 | const new_file_walk_result = self.walkInstruction( |
| ... | ... | @@ -601,14 +613,12 @@ fn walkInstruction( |
| 601 | 613 | &new_scope, |
| 602 | 614 | Zir.main_struct_inst, |
| 603 | 615 | ); |
| 604 | | // We re-access the hashmap in case it was modified |
| 605 | | // by walkInstruction() |
| 606 | | self.files.getPtr(new_file.file).?.analyzed = true; |
| 616 | |
| 607 | 617 | return new_file_walk_result; |
| 608 | 618 | }, |
| 609 | 619 | .block => { |
| 610 | | const res = DocData.WalkResult{ .comptimeExpr = self.comptimeExprs.items.len }; |
| 611 | | try self.comptimeExprs.append(self.arena, .{ |
| 620 | const res = DocData.WalkResult{ .comptimeExpr = self.comptime_exprs.items.len }; |
| 621 | try self.comptime_exprs.append(self.arena, .{ |
| 612 | 622 | .code = "if(banana) 1 else 0", |
| 613 | 623 | .typeRef = .{ .type = 0 }, |
| 614 | 624 | }); |
| ... | ... | @@ -669,7 +679,7 @@ fn walkInstruction( |
| 669 | 679 | // TODO: Actually, this is a good moment to check if |
| 670 | 680 | // the result is indeed a type!! |
| 671 | 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 | 684 | .int => operand.int.typeRef = dest_type_ref, |
| 675 | 685 | .@"struct" => operand.@"struct".typeRef = dest_type_ref, |
| ... | ... | @@ -731,35 +741,7 @@ fn walkInstruction( |
| 731 | 741 | // the analyzed data corresponding to the top-most decl of this path. |
| 732 | 742 | // We are now going to reverse loop over `path` to resolve each name |
| 733 | 743 | // to its corresponding index in `decls`. |
| 734 | | |
| 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 | | |
| 744 | try self.tryResolveDeclPath(file, path.items); |
| 763 | 745 | return DocData.WalkResult{ .declPath = path.items }; |
| 764 | 746 | }, |
| 765 | 747 | .int_type => { |
| ... | ... | @@ -841,6 +823,29 @@ fn walkInstruction( |
| 841 | 823 | return DocData.WalkResult{ .type = self.types.items.len - 1 }; |
| 842 | 824 | }, |
| 843 | 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 | 849 | const extended = data[inst_index].extended; |
| 845 | 850 | switch (extended.opcode) { |
| 846 | 851 | else => { |
| ... | ... | @@ -898,6 +903,9 @@ fn walkInstruction( |
| 898 | 903 | { |
| 899 | 904 | var it = file.zir.declIterator(@intCast(u32, inst_index)); |
| 900 | 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 | 909 | var decls_slot_index = decls_first_index; |
| 902 | 910 | while (it.next()) |d| : (decls_slot_index += 1) { |
| 903 | 911 | const decl_name_index = file.zir.extra[d.sub_index + 5]; |
| ... | ... | @@ -937,7 +945,7 @@ fn walkInstruction( |
| 937 | 945 | |
| 938 | 946 | self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items; |
| 939 | 947 | |
| 940 | | try self.types.append(self.arena, .{ |
| 948 | self.types.items[type_slot_index] = .{ |
| 941 | 949 | .Union = .{ |
| 942 | 950 | .name = "todo_name", |
| 943 | 951 | .src = self_ast_node_index, |
| ... | ... | @@ -945,9 +953,9 @@ fn walkInstruction( |
| 945 | 953 | .pubDecls = decl_indexes.items, |
| 946 | 954 | .fields = field_type_refs.items, |
| 947 | 955 | }, |
| 948 | | }); |
| 956 | }; |
| 949 | 957 | |
| 950 | | return DocData.WalkResult{ .type = self.types.items.len - 1 }; |
| 958 | return DocData.WalkResult{ .type = type_slot_index }; |
| 951 | 959 | }, |
| 952 | 960 | .enum_decl => { |
| 953 | 961 | var scope: Scope = .{ .parent = parent_scope }; |
| ... | ... | @@ -998,6 +1006,9 @@ fn walkInstruction( |
| 998 | 1006 | { |
| 999 | 1007 | var it = file.zir.declIterator(@intCast(u32, inst_index)); |
| 1000 | 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 | 1012 | var decls_slot_index = decls_first_index; |
| 1002 | 1013 | while (it.next()) |d| : (decls_slot_index += 1) { |
| 1003 | 1014 | const decl_name_index = file.zir.extra[d.sub_index + 5]; |
| ... | ... | @@ -1063,16 +1074,16 @@ fn walkInstruction( |
| 1063 | 1074 | |
| 1064 | 1075 | self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items; |
| 1065 | 1076 | |
| 1066 | | try self.types.append(self.arena, .{ |
| 1077 | self.types.items[type_slot_index] = .{ |
| 1067 | 1078 | .Enum = .{ |
| 1068 | 1079 | .name = "todo_name", |
| 1069 | 1080 | .src = self_ast_node_index, |
| 1070 | 1081 | .privDecls = priv_decl_indexes.items, |
| 1071 | 1082 | .pubDecls = decl_indexes.items, |
| 1072 | 1083 | }, |
| 1073 | | }); |
| 1084 | }; |
| 1074 | 1085 | |
| 1075 | | return DocData.WalkResult{ .type = self.types.items.len - 1 }; |
| 1086 | return DocData.WalkResult{ .type = type_slot_index }; |
| 1076 | 1087 | }, |
| 1077 | 1088 | .struct_decl => { |
| 1078 | 1089 | var scope: Scope = .{ .parent = parent_scope }; |
| ... | ... | @@ -1116,6 +1127,9 @@ fn walkInstruction( |
| 1116 | 1127 | { |
| 1117 | 1128 | var it = file.zir.declIterator(@intCast(u32, inst_index)); |
| 1118 | 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 | 1133 | var decls_slot_index = decls_first_index; |
| 1120 | 1134 | while (it.next()) |d| : (decls_slot_index += 1) { |
| 1121 | 1135 | const decl_name_index = file.zir.extra[d.sub_index + 5]; |
| ... | ... | @@ -1149,7 +1163,7 @@ fn walkInstruction( |
| 1149 | 1163 | |
| 1150 | 1164 | self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items; |
| 1151 | 1165 | |
| 1152 | | try self.types.append(self.arena, .{ |
| 1166 | self.types.items[type_slot_index] = .{ |
| 1153 | 1167 | .Struct = .{ |
| 1154 | 1168 | .name = "todo_name", |
| 1155 | 1169 | .src = self_ast_node_index, |
| ... | ... | @@ -1157,9 +1171,9 @@ fn walkInstruction( |
| 1157 | 1171 | .pubDecls = decl_indexes.items, |
| 1158 | 1172 | .fields = field_type_refs.items, |
| 1159 | 1173 | }, |
| 1160 | | }); |
| 1174 | }; |
| 1161 | 1175 | |
| 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 | 1324 | }; |
| 1311 | 1325 | self.decls.items[decl_being_tested].decltest = ast_node_index; |
| 1312 | 1326 | self.decls.items[decls_slot_index] = .{ |
| 1327 | ._analyzed = true, |
| 1313 | 1328 | .name = "test", |
| 1314 | 1329 | .src = ast_node_index, |
| 1315 | 1330 | .value = .{ .type = 0 }, |
| ... | ... | @@ -1368,17 +1383,110 @@ fn walkDecls( |
| 1368 | 1383 | // }; |
| 1369 | 1384 | |
| 1370 | 1385 | self.decls.items[decls_slot_index] = .{ |
| 1386 | ._analyzed = true, |
| 1371 | 1387 | .name = name, |
| 1372 | 1388 | .src = ast_node_index, |
| 1373 | 1389 | // .typeRef = decl_type_ref, |
| 1374 | 1390 | .value = walk_result, |
| 1375 | 1391 | .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 | } |
| 1378 | 1406 | |
| 1379 | 1407 | return extra_index; |
| 1380 | 1408 | } |
| 1381 | 1409 | |
| 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. |
| 1415 | fn 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 | |
| 1382 | 1490 | fn collectUnionFieldInfo( |
| 1383 | 1491 | self: *Autodoc, |
| 1384 | 1492 | file: *File, |