autodoc: improve comments

This commit is contained in:
Loris Cro 2022-03-09 19:43:20 +01:00 committed by Andrew Kelley
parent 4f949163a5
commit d745dde54f

View File

@ -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;