| ... | @@ -9,12 +9,19 @@ const Ref = Zir.Inst.Ref; | ... | @@ -9,12 +9,19 @@ const Ref = Zir.Inst.Ref; |
| 9 | module: *Module, | 9 | module: *Module, |
| 10 | doc_location: Compilation.EmitLoc, | 10 | doc_location: Compilation.EmitLoc, |
| 11 | arena: std.mem.Allocator, | 11 | arena: std.mem.Allocator, |
| | 12 | |
| | 13 | // The goal of autodoc is to fill up these arrays |
| | 14 | // that will then be serialized as JSON and consumed |
| | 15 | // by the JS frontend. |
| 12 | files: std.AutoHashMapUnmanaged(*File, usize) = .{}, | 16 | files: std.AutoHashMapUnmanaged(*File, usize) = .{}, |
| 13 | calls: std.ArrayListUnmanaged(DocData.Call) = .{}, | 17 | calls: std.ArrayListUnmanaged(DocData.Call) = .{}, |
| 14 | types: std.ArrayListUnmanaged(DocData.Type) = .{}, | 18 | types: std.ArrayListUnmanaged(DocData.Type) = .{}, |
| 15 | decls: std.ArrayListUnmanaged(DocData.Decl) = .{}, | 19 | decls: std.ArrayListUnmanaged(DocData.Decl) = .{}, |
| 16 | ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{}, | 20 | ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{}, |
| 17 | comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{}, | 21 | comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{}, |
| | 22 | |
| | 23 | // These fields hold temporary state of the analysis process |
| | 24 | // and are mainly used by the decl path resolving algorithm. |
| 18 | pending_decl_paths: std.AutoHashMapUnmanaged( | 25 | pending_decl_paths: std.AutoHashMapUnmanaged( |
| 19 | *usize, // pointer to declpath head (ie `&decl_path[0]`) | 26 | *usize, // pointer to declpath head (ie `&decl_path[0]`) |
| 20 | std.ArrayListUnmanaged(DeclPathResumeInfo), | 27 | std.ArrayListUnmanaged(DeclPathResumeInfo), |
| ... | @@ -47,6 +54,7 @@ pub fn deinit(_: *Autodoc) void { | ... | @@ -47,6 +54,7 @@ pub fn deinit(_: *Autodoc) void { |
| 47 | arena_allocator.deinit(); | 54 | arena_allocator.deinit(); |
| 48 | } | 55 | } |
| 49 | | 56 | |
| | 57 | /// The entry point of the Autodoc generation process. |
| 50 | pub fn generateZirData(self: *Autodoc) !void { | 58 | pub fn generateZirData(self: *Autodoc) !void { |
| 51 | if (self.doc_location.directory) |dir| { | 59 | if (self.doc_location.directory) |dir| { |
| 52 | if (dir.path) |path| { | 60 | if (dir.path) |path| { |
| ... | @@ -66,7 +74,7 @@ pub fn generateZirData(self: *Autodoc) !void { | ... | @@ -66,7 +74,7 @@ pub fn generateZirData(self: *Autodoc) !void { |
| 66 | defer self.arena.free(abs_root_path); | 74 | defer self.arena.free(abs_root_path); |
| 67 | const file = self.module.import_table.get(abs_root_path).?; | 75 | const file = self.module.import_table.get(abs_root_path).?; |
| 68 | | 76 | |
| 69 | // append all the types in Zir.Inst.Ref | 77 | // Append all the types in Zir.Inst.Ref. |
| 70 | { | 78 | { |
| 71 | try self.types.append(self.arena, .{ | 79 | try self.types.append(self.arena, .{ |
| 72 | .ComptimeExpr = .{ .name = "ComptimeExpr" }, | 80 | .ComptimeExpr = .{ .name = "ComptimeExpr" }, |
| ... | @@ -80,9 +88,8 @@ pub fn generateZirData(self: *Autodoc) !void { | ... | @@ -80,9 +88,8 @@ pub fn generateZirData(self: *Autodoc) !void { |
| 80 | self.arena, | 88 | self.arena, |
| 81 | switch (@intToEnum(Ref, i)) { | 89 | switch (@intToEnum(Ref, i)) { |
| 82 | else => blk: { | 90 | else => blk: { |
| 83 | //std.debug.print("TODO: categorize `{s}` in typeKinds\n", .{ | 91 | // TODO: map the remaining refs to a correct type |
| 84 | // @tagName(t), | 92 | // instead of just assinging "array" to them. |
| 85 | //}); | | |
| 86 | break :blk .{ | 93 | break :blk .{ |
| 87 | .Array = .{ | 94 | .Array = .{ |
| 88 | .len = 1, | 95 | .len = 1, |
| ... | @@ -213,6 +220,8 @@ pub fn generateZirData(self: *Autodoc) !void { | ... | @@ -213,6 +220,8 @@ pub fn generateZirData(self: *Autodoc) !void { |
| 213 | special_dir.copyFile("index.html", output_dir, "index.html", .{}) catch unreachable; | 220 | special_dir.copyFile("index.html", output_dir, "index.html", .{}) catch unreachable; |
| 214 | } | 221 | } |
| 215 | | 222 | |
| | 223 | /// Represents a chain of scopes, used to resolve decl references to the |
| | 224 | /// corresponding entry in `self.decls`. |
| 216 | const Scope = struct { | 225 | const Scope = struct { |
| 217 | parent: ?*Scope, | 226 | parent: ?*Scope, |
| 218 | map: std.AutoHashMapUnmanaged(u32, usize) = .{}, // index into `decls` | 227 | map: std.AutoHashMapUnmanaged(u32, usize) = .{}, // index into `decls` |
| ... | @@ -237,6 +246,7 @@ const Scope = struct { | ... | @@ -237,6 +246,7 @@ const Scope = struct { |
| 237 | } | 246 | } |
| 238 | }; | 247 | }; |
| 239 | | 248 | |
| | 249 | /// The output of our analysis process. |
| 240 | const DocData = struct { | 250 | const DocData = struct { |
| 241 | typeKinds: []const []const u8 = std.meta.fieldNames(DocTypeKinds), | 251 | typeKinds: []const []const u8 = std.meta.fieldNames(DocTypeKinds), |
| 242 | rootPkg: u32 = 0, | 252 | rootPkg: u32 = 0, |
| ... | @@ -290,6 +300,17 @@ const DocData = struct { | ... | @@ -290,6 +300,17 @@ const DocData = struct { |
| 290 | args: []WalkResult, | 300 | args: []WalkResult, |
| 291 | ret: WalkResult, | 301 | ret: WalkResult, |
| 292 | }; | 302 | }; |
| | 303 | |
| | 304 | /// All the type "families" as described by `std.builtin.TypeId` |
| | 305 | /// plus a couple extra that are unique to our use case. |
| | 306 | /// |
| | 307 | /// `Unanalyzed` is used so that we can refer to types that have started |
| | 308 | /// analysis but that haven't been fully analyzed yet (in case we find |
| | 309 | /// self-referential stuff, like `@This()`). |
| | 310 | /// |
| | 311 | /// `ComptimeExpr` represents the result of a piece of comptime logic |
| | 312 | /// that we weren't able to analyze fully. Examples of that are comptime |
| | 313 | /// function calls and comptime if / switch / ... expressions. |
| 293 | const DocTypeKinds = blk: { | 314 | const DocTypeKinds = blk: { |
| 294 | var info = @typeInfo(std.builtin.TypeId); | 315 | var info = @typeInfo(std.builtin.TypeId); |
| 295 | const original_len = info.Enum.fields.len; | 316 | const original_len = info.Enum.fields.len; |
| ... | @@ -474,12 +495,25 @@ const DocData = struct { | ... | @@ -474,12 +495,25 @@ const DocData = struct { |
| 474 | } | 495 | } |
| 475 | }; | 496 | }; |
| 476 | | 497 | |
| | 498 | /// A DeclPath represents an expression such as `foo.bar.baz` where each |
| | 499 | /// component has been resolved to a corresponding index in `self.decls`. |
| | 500 | /// If a DeclPath has a component that can't be fully solved (eg the |
| | 501 | /// function call in `foo.bar().baz`), then it will be solved up until the |
| | 502 | /// unresolved component, leaving the remaining part unresolved. |
| | 503 | /// |
| | 504 | /// Note that DeclPaths are currently stored in inverse order: the innermost |
| | 505 | /// component is at index 0. |
| 477 | const DeclPath = struct { | 506 | const DeclPath = struct { |
| 478 | path: []usize, // indexes in `decls` | 507 | path: []usize, // indexes in `decls` |
| 479 | hasCte: bool = false, // a prefix of this path could not be resolved | 508 | hasCte: bool = false, // a prefix of this path could not be resolved |
| 480 | // TODO: make hasCte return the actual index where the cte is! | 509 | // TODO: make hasCte return the actual index where the cte is! |
| 481 | }; | 510 | }; |
| 482 | | 511 | |
| | 512 | /// A TypeRef is a subset of WalkResult that refers a type in a direct or |
| | 513 | /// indirect manner. |
| | 514 | /// |
| | 515 | /// An example of directness is `const foo = struct {...};`. |
| | 516 | /// An example of indidirectness is `const bar = foo;`. |
| 483 | const TypeRef = union(enum) { | 517 | const TypeRef = union(enum) { |
| 484 | unspecified, | 518 | unspecified, |
| 485 | declPath: DeclPath, | 519 | declPath: DeclPath, |
| ... | @@ -488,7 +522,7 @@ const DocData = struct { | ... | @@ -488,7 +522,7 @@ const DocData = struct { |
| 488 | // TODO: maybe we should not consider calls to be typerefs and instread | 522 | // TODO: maybe we should not consider calls to be typerefs and instread |
| 489 | // directly refer to their return value. The problem at the moment | 523 | // directly refer to their return value. The problem at the moment |
| 490 | // is that we can't analyze function calls at all. | 524 | // is that we can't analyze function calls at all. |
| 491 | call: usize, // index in `call` | 525 | call: usize, // index in `calls` |
| 492 | | 526 | |
| 493 | pub fn jsonStringify( | 527 | pub fn jsonStringify( |
| 494 | self: TypeRef, | 528 | self: TypeRef, |
| ... | @@ -518,6 +552,12 @@ const DocData = struct { | ... | @@ -518,6 +552,12 @@ const DocData = struct { |
| 518 | } | 552 | } |
| 519 | }; | 553 | }; |
| 520 | | 554 | |
| | 555 | /// A WalkResult represents the result of the analysis process done to a |
| | 556 | /// declaration. This includes: decls, fields, etc. |
| | 557 | /// |
| | 558 | /// The data in WalkResult is mostly normalized, which means that a |
| | 559 | /// WalkResult that results in a type definition will hold an index into |
| | 560 | /// `self.types`. |
| 521 | const WalkResult = union(enum) { | 561 | const WalkResult = union(enum) { |
| 522 | comptimeExpr: usize, // index in `comptimeExprs` | 562 | comptimeExpr: usize, // index in `comptimeExprs` |
| 523 | void, | 563 | void, |
| ... | @@ -637,6 +677,14 @@ const DocData = struct { | ... | @@ -637,6 +677,14 @@ const DocData = struct { |
| 637 | }; | 677 | }; |
| 638 | }; | 678 | }; |
| 639 | | 679 | |
| | 680 | /// Called when we need to analyze a Zir instruction. |
| | 681 | /// For example it gets called by `generateZirData` on instruction 0, |
| | 682 | /// which represents the top-level struct corresponding to the root file. |
| | 683 | /// Note that in some situations where we're analyzing code that only allows |
| | 684 | /// for a limited subset of Zig syntax, we don't always resort to calling |
| | 685 | /// `walkInstruction` and instead sometimes we handle Zir directly. |
| | 686 | /// The best example of that are instructions corresponding to function |
| | 687 | /// params, as those can only occur while analyzing a function definition. |
| 640 | fn walkInstruction( | 688 | fn walkInstruction( |
| 641 | self: *Autodoc, | 689 | self: *Autodoc, |
| 642 | file: *File, | 690 | file: *File, |
| ... | @@ -1399,13 +1447,13 @@ fn walkInstruction( | ... | @@ -1399,13 +1447,13 @@ fn walkInstruction( |
| 1399 | } | 1447 | } |
| 1400 | } | 1448 | } |
| 1401 | | 1449 | |
| 1402 | /// Called by `walkInstruction` when encountering a container type, | 1450 | /// Called by `walkInstruction` when encountering a container type. |
| 1403 | /// iterates over all decl definitions in its body. | 1451 | /// Iterates over all decl definitions in its body and it also analyzes each |
| 1404 | /// It also analyzes each decl's body recursively. | 1452 | /// decl's body recursively by calling into `walkInstruction`. |
| 1405 | /// | 1453 | /// |
| 1406 | /// Does not append to `self.decls` directly because `walkInstruction` | 1454 | /// Does not append to `self.decls` directly because `walkInstruction` |
| 1407 | /// is expected to (look-ahead) scan all decls and reserve `body_len` | 1455 | /// is expected to look-ahead scan all decls and reserve `body_len` |
| 1408 | /// slots in `self.decls`, which are then filled out by `walkDecls`. | 1456 | /// slots in `self.decls`, which are then filled out by this function. |
| 1409 | fn walkDecls( | 1457 | fn walkDecls( |
| 1410 | self: *Autodoc, | 1458 | self: *Autodoc, |
| 1411 | file: *File, | 1459 | file: *File, |
| ... | @@ -1634,10 +1682,27 @@ fn walkDecls( | ... | @@ -1634,10 +1682,27 @@ fn walkDecls( |
| 1634 | } | 1682 | } |
| 1635 | | 1683 | |
| 1636 | /// An unresolved path has a decl index at its end, while every other element | 1684 | /// An unresolved path has a decl index at its end, while every other element |
| 1637 | /// is an index into the string table. Resolving means resolving iteratively | 1685 | /// is an index into the string table. Resolving means iteratively map each |
| 1638 | /// each string into a decl_index. If we encounter an unanalyzed decl during | 1686 | /// string to a decl_index. |
| 1639 | /// the process, we append the unsolved sub-path to `self.decl_paths_pending_on_decls` | 1687 | /// |
| 1640 | /// and bail out. | 1688 | /// If we encounter an unanalyzed decl during the process, we append the |
| | 1689 | /// unsolved sub-path to `self.decl_paths_pending_on_decls` and bail out. |
| | 1690 | /// Same happens when a decl holds a type definition that hasn't been fully |
| | 1691 | /// analyzed yet (except that we append to `self.decl_paths_pending_on_types`. |
| | 1692 | /// |
| | 1693 | /// When a decl or a type is fully analyzed if will then check if there's any |
| | 1694 | /// pending decl path blocked on it and, if any, will progress their resolution |
| | 1695 | /// by calling tryResolveDeclPath again. |
| | 1696 | /// |
| | 1697 | /// Decl paths can also depend on other decl paths. See |
| | 1698 | /// `self.pending_decl_paths` for more info. |
| | 1699 | /// |
| | 1700 | /// A decl path that has a component that resolves into a comptimeExpr will |
| | 1701 | /// give up its resolution process entirely. |
| | 1702 | /// |
| | 1703 | /// TODO: when giving up, translate remaining string indexes into data that |
| | 1704 | /// can be used by the frontend. Requires implementing a frontend string |
| | 1705 | /// table. |
| 1641 | fn tryResolveDeclPath( | 1706 | fn tryResolveDeclPath( |
| 1642 | self: *Autodoc, | 1707 | self: *Autodoc, |
| 1643 | /// File from which the decl path originates. | 1708 | /// File from which the decl path originates. |
| ... | @@ -1920,6 +1985,8 @@ fn collectStructFieldInfo( | ... | @@ -1920,6 +1985,8 @@ fn collectStructFieldInfo( |
| 1920 | } | 1985 | } |
| 1921 | } | 1986 | } |
| 1922 | | 1987 | |
| | 1988 | /// A Zir Ref can either refer to common types and values, or to a Zir index. |
| | 1989 | /// WalkRef resolves common cases and delegates to `walkInstruction` otherwise. |
| 1923 | fn walkRef( | 1990 | fn walkRef( |
| 1924 | self: *Autodoc, | 1991 | self: *Autodoc, |
| 1925 | file: *File, | 1992 | file: *File, |
| ... | @@ -2014,6 +2081,9 @@ fn walkRef( | ... | @@ -2014,6 +2081,9 @@ fn walkRef( |
| 2014 | } | 2081 | } |
| 2015 | } | 2082 | } |
| 2016 | | 2083 | |
| | 2084 | /// Maps some `DocData.WalkResult` cases to `DocData.TypeRef`. |
| | 2085 | /// Correct code should never cause this function to fail but |
| | 2086 | /// incorrect code might (eg: `const foo: 5 = undefined;`) |
| 2017 | fn walkResultToTypeRef(wr: DocData.WalkResult) DocData.TypeRef { | 2087 | fn walkResultToTypeRef(wr: DocData.WalkResult) DocData.TypeRef { |
| 2018 | return switch (wr) { | 2088 | return switch (wr) { |
| 2019 | else => std.debug.panic( | 2089 | else => std.debug.panic( |
| ... | @@ -2027,6 +2097,9 @@ fn walkResultToTypeRef(wr: DocData.WalkResult) DocData.TypeRef { | ... | @@ -2027,6 +2097,9 @@ fn walkResultToTypeRef(wr: DocData.WalkResult) DocData.TypeRef { |
| 2027 | }; | 2097 | }; |
| 2028 | } | 2098 | } |
| 2029 | | 2099 | |
| | 2100 | /// Given a WalkResult, tries to find its type. |
| | 2101 | /// Used to analyze instructions like `array_init`, which require us to |
| | 2102 | /// inspect its first element to find out the array type. |
| 2030 | fn typeOfWalkResult(wr: DocData.WalkResult) DocData.TypeRef { | 2103 | fn typeOfWalkResult(wr: DocData.WalkResult) DocData.TypeRef { |
| 2031 | return switch (wr) { | 2104 | return switch (wr) { |
| 2032 | else => std.debug.panic( | 2105 | else => std.debug.panic( |
| ... | @@ -2040,9 +2113,6 @@ fn typeOfWalkResult(wr: DocData.WalkResult) DocData.TypeRef { | ... | @@ -2040,9 +2113,6 @@ fn typeOfWalkResult(wr: DocData.WalkResult) DocData.TypeRef { |
| 2040 | }; | 2113 | }; |
| 2041 | } | 2114 | } |
| 2042 | | 2115 | |
| 2043 | //fn collectParamInfo(self: *Autodoc, file: *File, scope: *Scope, inst_idx: Zir.Index) void { | | |
| 2044 | | | |
| 2045 | //} | | |
| 2046 | fn getBlockInlineBreak(zir: Zir, inst_index: usize) Zir.Inst.Ref { | 2116 | fn getBlockInlineBreak(zir: Zir, inst_index: usize) Zir.Inst.Ref { |
| 2047 | const data = zir.instructions.items(.data); | 2117 | const data = zir.instructions.items(.data); |
| 2048 | const pl_node = data[inst_index].pl_node; | 2118 | const pl_node = data[inst_index].pl_node; |