Commit d745dde54f
Changed files (1)
src
src/Autodoc.zig
@@ -9,12 +9,19 @@ const Ref = Zir.Inst.Ref;
module: *Module,
doc_location: Compilation.EmitLoc,
arena: std.mem.Allocator,
+
+// The goal of autodoc is to fill up these arrays
+// that will then be serialized as JSON and consumed
+// by the JS frontend.
files: std.AutoHashMapUnmanaged(*File, usize) = .{},
calls: std.ArrayListUnmanaged(DocData.Call) = .{},
types: std.ArrayListUnmanaged(DocData.Type) = .{},
decls: std.ArrayListUnmanaged(DocData.Decl) = .{},
ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{},
comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},
+
+// These fields hold temporary state of the analysis process
+// and are mainly used by the decl path resolving algorithm.
pending_decl_paths: std.AutoHashMapUnmanaged(
*usize, // pointer to declpath head (ie `&decl_path[0]`)
std.ArrayListUnmanaged(DeclPathResumeInfo),
@@ -47,6 +54,7 @@ pub fn deinit(_: *Autodoc) void {
arena_allocator.deinit();
}
+/// The entry point of the Autodoc generation process.
pub fn generateZirData(self: *Autodoc) !void {
if (self.doc_location.directory) |dir| {
if (dir.path) |path| {
@@ -66,7 +74,7 @@ pub fn generateZirData(self: *Autodoc) !void {
defer self.arena.free(abs_root_path);
const file = self.module.import_table.get(abs_root_path).?;
- // append all the types in Zir.Inst.Ref
+ // Append all the types in Zir.Inst.Ref.
{
try self.types.append(self.arena, .{
.ComptimeExpr = .{ .name = "ComptimeExpr" },
@@ -80,9 +88,8 @@ pub fn generateZirData(self: *Autodoc) !void {
self.arena,
switch (@intToEnum(Ref, i)) {
else => blk: {
- //std.debug.print("TODO: categorize `{s}` in typeKinds\n", .{
- // @tagName(t),
- //});
+ // TODO: map the remaining refs to a correct type
+ // instead of just assinging "array" to them.
break :blk .{
.Array = .{
.len = 1,
@@ -213,6 +220,8 @@ pub fn generateZirData(self: *Autodoc) !void {
special_dir.copyFile("index.html", output_dir, "index.html", .{}) catch unreachable;
}
+/// Represents a chain of scopes, used to resolve decl references to the
+/// corresponding entry in `self.decls`.
const Scope = struct {
parent: ?*Scope,
map: std.AutoHashMapUnmanaged(u32, usize) = .{}, // index into `decls`
@@ -237,6 +246,7 @@ const Scope = struct {
}
};
+/// The output of our analysis process.
const DocData = struct {
typeKinds: []const []const u8 = std.meta.fieldNames(DocTypeKinds),
rootPkg: u32 = 0,
@@ -290,6 +300,17 @@ const DocData = struct {
args: []WalkResult,
ret: WalkResult,
};
+
+ /// All the type "families" as described by `std.builtin.TypeId`
+ /// plus a couple extra that are unique to our use case.
+ ///
+ /// `Unanalyzed` is used so that we can refer to types that have started
+ /// analysis but that haven't been fully analyzed yet (in case we find
+ /// self-referential stuff, like `@This()`).
+ ///
+ /// `ComptimeExpr` represents the result of a piece of comptime logic
+ /// that we weren't able to analyze fully. Examples of that are comptime
+ /// function calls and comptime if / switch / ... expressions.
const DocTypeKinds = blk: {
var info = @typeInfo(std.builtin.TypeId);
const original_len = info.Enum.fields.len;
@@ -474,12 +495,25 @@ const DocData = struct {
}
};
+ /// A DeclPath represents an expression such as `foo.bar.baz` where each
+ /// component has been resolved to a corresponding index in `self.decls`.
+ /// If a DeclPath has a component that can't be fully solved (eg the
+ /// function call in `foo.bar().baz`), then it will be solved up until the
+ /// unresolved component, leaving the remaining part unresolved.
+ ///
+ /// Note that DeclPaths are currently stored in inverse order: the innermost
+ /// component is at index 0.
const DeclPath = struct {
path: []usize, // indexes in `decls`
hasCte: bool = false, // a prefix of this path could not be resolved
// TODO: make hasCte return the actual index where the cte is!
};
+ /// A TypeRef is a subset of WalkResult that refers a type in a direct or
+ /// indirect manner.
+ ///
+ /// An example of directness is `const foo = struct {...};`.
+ /// An example of indidirectness is `const bar = foo;`.
const TypeRef = union(enum) {
unspecified,
declPath: DeclPath,
@@ -488,7 +522,7 @@ const DocData = struct {
// TODO: maybe we should not consider calls to be typerefs and instread
// directly refer to their return value. The problem at the moment
// is that we can't analyze function calls at all.
- call: usize, // index in `call`
+ call: usize, // index in `calls`
pub fn jsonStringify(
self: TypeRef,
@@ -518,6 +552,12 @@ const DocData = struct {
}
};
+ /// A WalkResult represents the result of the analysis process done to a
+ /// declaration. This includes: decls, fields, etc.
+ ///
+ /// The data in WalkResult is mostly normalized, which means that a
+ /// WalkResult that results in a type definition will hold an index into
+ /// `self.types`.
const WalkResult = union(enum) {
comptimeExpr: usize, // index in `comptimeExprs`
void,
@@ -637,6 +677,14 @@ const DocData = struct {
};
};
+/// Called when we need to analyze a Zir instruction.
+/// For example it gets called by `generateZirData` on instruction 0,
+/// which represents the top-level struct corresponding to the root file.
+/// Note that in some situations where we're analyzing code that only allows
+/// for a limited subset of Zig syntax, we don't always resort to calling
+/// `walkInstruction` and instead sometimes we handle Zir directly.
+/// The best example of that are instructions corresponding to function
+/// params, as those can only occur while analyzing a function definition.
fn walkInstruction(
self: *Autodoc,
file: *File,
@@ -1399,13 +1447,13 @@ fn walkInstruction(
}
}
-/// Called by `walkInstruction` when encountering a container type,
-/// iterates over all decl definitions in its body.
-/// It also analyzes each decl's body recursively.
+/// Called by `walkInstruction` when encountering a container type.
+/// Iterates over all decl definitions in its body and it also analyzes each
+/// decl's body recursively by calling into `walkInstruction`.
///
/// Does not append to `self.decls` directly because `walkInstruction`
-/// is expected to (look-ahead) scan all decls and reserve `body_len`
-/// slots in `self.decls`, which are then filled out by `walkDecls`.
+/// is expected to look-ahead scan all decls and reserve `body_len`
+/// slots in `self.decls`, which are then filled out by this function.
fn walkDecls(
self: *Autodoc,
file: *File,
@@ -1634,10 +1682,27 @@ fn walkDecls(
}
/// An unresolved path has a decl index at its end, while every other element
-/// is an index into the string table. Resolving means resolving iteratively
-/// each string into a decl_index. If we encounter an unanalyzed decl during
-/// the process, we append the unsolved sub-path to `self.decl_paths_pending_on_decls`
-/// and bail out.
+/// is an index into the string table. Resolving means iteratively map each
+/// string to a decl_index.
+///
+/// If we encounter an unanalyzed decl during the process, we append the
+/// unsolved sub-path to `self.decl_paths_pending_on_decls` and bail out.
+/// Same happens when a decl holds a type definition that hasn't been fully
+/// analyzed yet (except that we append to `self.decl_paths_pending_on_types`.
+///
+/// When a decl or a type is fully analyzed if will then check if there's any
+/// pending decl path blocked on it and, if any, will progress their resolution
+/// by calling tryResolveDeclPath again.
+///
+/// Decl paths can also depend on other decl paths. See
+/// `self.pending_decl_paths` for more info.
+///
+/// A decl path that has a component that resolves into a comptimeExpr will
+/// give up its resolution process entirely.
+///
+/// TODO: when giving up, translate remaining string indexes into data that
+/// can be used by the frontend. Requires implementing a frontend string
+/// table.
fn tryResolveDeclPath(
self: *Autodoc,
/// File from which the decl path originates.
@@ -1920,6 +1985,8 @@ fn collectStructFieldInfo(
}
}
+/// A Zir Ref can either refer to common types and values, or to a Zir index.
+/// WalkRef resolves common cases and delegates to `walkInstruction` otherwise.
fn walkRef(
self: *Autodoc,
file: *File,
@@ -2014,6 +2081,9 @@ fn walkRef(
}
}
+/// Maps some `DocData.WalkResult` cases to `DocData.TypeRef`.
+/// Correct code should never cause this function to fail but
+/// incorrect code might (eg: `const foo: 5 = undefined;`)
fn walkResultToTypeRef(wr: DocData.WalkResult) DocData.TypeRef {
return switch (wr) {
else => std.debug.panic(
@@ -2027,6 +2097,9 @@ fn walkResultToTypeRef(wr: DocData.WalkResult) DocData.TypeRef {
};
}
+/// Given a WalkResult, tries to find its type.
+/// Used to analyze instructions like `array_init`, which require us to
+/// inspect its first element to find out the array type.
fn typeOfWalkResult(wr: DocData.WalkResult) DocData.TypeRef {
return switch (wr) {
else => std.debug.panic(
@@ -2040,9 +2113,6 @@ fn typeOfWalkResult(wr: DocData.WalkResult) DocData.TypeRef {
};
}
-//fn collectParamInfo(self: *Autodoc, file: *File, scope: *Scope, inst_idx: Zir.Index) void {
-
-//}
fn getBlockInlineBreak(zir: Zir, inst_index: usize) Zir.Inst.Ref {
const data = zir.instructions.items(.data);
const pl_node = data[inst_index].pl_node;