aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLoris Cro <kappaloris@gmail.com>2022-03-02 18:36:44 +0100
committerAndrew Kelley <andrew@ziglang.org>2022-07-19 19:10:11 -0700
commit028c8a3c91286fe42d915cfe35976e3392895447 (patch)
tree70e03b02ed28fd07ea7ae28c5ffa5f9fa789e96c /src
parent580e633777e55bcfad6f039f8acfa338d686368e (diff)
downloadzig-028c8a3c91286fe42d915cfe35976e3392895447.tar.gz
zig-028c8a3c91286fe42d915cfe35976e3392895447.zip
autodoc: added support for same-file lazy resolution of decl paths
Diffstat (limited to 'src')
-rw-r--r--src/Autodoc.zig248
1 files changed, 178 insertions, 70 deletions
diff --git a/src/Autodoc.zig b/src/Autodoc.zig
index 809392862d..3b911bb205 100644
--- a/src/Autodoc.zig
+++ b/src/Autodoc.zig
@@ -9,11 +9,24 @@ const Ref = Zir.Inst.Ref;
module: *Module,
doc_location: Compilation.EmitLoc,
arena: std.mem.Allocator,
-files: std.AutoHashMapUnmanaged(*File, DocData.AutodocFile) = .{},
+files: std.AutoHashMapUnmanaged(*File, usize) = .{},
types: std.ArrayListUnmanaged(DocData.Type) = .{},
decls: std.ArrayListUnmanaged(DocData.Decl) = .{},
ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{},
-comptimeExprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},
+comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},
+decl_paths_pending_on_decls: std.AutoHashMapUnmanaged(
+ usize,
+ std.ArrayListUnmanaged(DeclPathResumeInfo),
+) = .{},
+decl_paths_pending_on_types: std.AutoHashMapUnmanaged(
+ usize,
+ std.ArrayListUnmanaged(DeclPathResumeInfo),
+) = .{},
+
+const DeclPathResumeInfo = struct {
+ file: *File,
+ path: []usize,
+};
var arena_allocator: std.heap.ArenaAllocator = undefined;
pub fn init(m: *Module, doc_location: Compilation.EmitLoc) Autodoc {
@@ -129,22 +142,23 @@ pub fn generateZirData(self: *Autodoc) !void {
var root_scope = Scope{ .parent = null };
try self.ast_nodes.append(self.arena, .{ .name = "(root)" });
- try self.files.put(self.arena, file, .{
- .analyzed = false,
- .root_struct = self.types.items.len,
- });
+ try self.files.put(self.arena, file, self.types.items.len);
const main_type_index = try self.walkInstruction(file, &root_scope, Zir.main_struct_inst);
- self.files.getPtr(file).?.analyzed = true;
- // TODO: solve every single pending declpath whose analysis
- // was delayed because of circular imports.
+ if (self.decl_paths_pending_on_decls.count() > 0) {
+ @panic("some decl paths were never fully analized (pending on decls)");
+ }
+
+ if (self.decl_paths_pending_on_types.count() > 0) {
+ @panic("some decl paths were never fully analized (pending on types)");
+ }
var data = DocData{
.files = .{ .data = self.files },
.types = self.types.items,
.decls = self.decls.items,
.astNodes = self.ast_nodes.items,
- .comptimeExprs = self.comptimeExprs.items,
+ .comptimeExprs = self.comptime_exprs.items,
};
data.packages[0].main = main_type_index.type;
@@ -232,7 +246,7 @@ const DocData = struct {
astNodes: []AstNode,
files: struct {
// this struct is a temporary hack to support json serialization
- data: std.AutoHashMapUnmanaged(*File, AutodocFile),
+ data: std.AutoHashMapUnmanaged(*File, usize),
pub fn jsonStringify(
self: @This(),
opt: std.json.StringifyOptions,
@@ -248,7 +262,7 @@ const DocData = struct {
if (options.whitespace) |ws| try ws.outputIndent(w);
try w.print("\"{s}\": {d}", .{
kv.key_ptr.*.sub_file_path,
- kv.value_ptr.root_struct,
+ kv.value_ptr.*,
});
if (idx != self.data.count() - 1) try w.writeByte(',');
try w.writeByte('\n');
@@ -261,17 +275,17 @@ const DocData = struct {
decls: []Decl,
comptimeExprs: []ComptimeExpr,
- const AutodocFile = struct {
- analyzed: bool, // omitted in json data
- root_struct: usize, // index into `types`
- };
-
const DocTypeKinds = blk: {
var info = @typeInfo(std.builtin.TypeId);
- info.Enum.fields = info.Enum.fields ++ [1]std.builtin.TypeInfo.EnumField{
+ const original_len = info.Enum.fields.len;
+ info.Enum.fields = info.Enum.fields ++ [2]std.builtin.TypeInfo.EnumField{
.{
.name = "ComptimeExpr",
- .value = info.Enum.fields.len,
+ .value = original_len,
+ },
+ .{
+ .name = "Unanalyzed",
+ .value = original_len + 1,
},
};
break :blk @Type(info);
@@ -298,6 +312,7 @@ const DocData = struct {
value: WalkResult,
// The index in astNodes of the `test declname { }` node
decltest: ?usize = null,
+ _analyzed: bool, // omitted in json data
};
const AstNode = struct {
@@ -310,6 +325,7 @@ const DocData = struct {
};
const Type = union(DocTypeKinds) {
+ Unanalyzed: void,
Type: struct { name: []const u8 },
Void: struct { name: []const u8 },
Bool: struct { name: []const u8 },
@@ -480,7 +496,7 @@ const DocData = struct {
@"struct": Struct,
bool: bool,
type: usize, // index in `types`
- declPath: []usize, // indices in `decls`
+ declPath: []usize, // indices in `decl`
int: struct {
typeRef: TypeRef,
value: usize, // direct value
@@ -584,16 +600,12 @@ fn walkInstruction(
// importFile cannot error out since all files
// are already loaded at this point
const new_file = self.module.importFile(file, path) catch unreachable;
-
const result = try self.files.getOrPut(self.arena, new_file.file);
if (result.found_existing) {
- return DocData.WalkResult{ .type = result.value_ptr.root_struct };
+ return DocData.WalkResult{ .type = result.value_ptr.* };
}
- result.value_ptr.* = .{
- .analyzed = false,
- .root_struct = self.types.items.len,
- };
+ result.value_ptr.* = self.types.items.len;
var new_scope = Scope{ .parent = null };
const new_file_walk_result = self.walkInstruction(
@@ -601,14 +613,12 @@ fn walkInstruction(
&new_scope,
Zir.main_struct_inst,
);
- // We re-access the hashmap in case it was modified
- // by walkInstruction()
- self.files.getPtr(new_file.file).?.analyzed = true;
+
return new_file_walk_result;
},
.block => {
- const res = DocData.WalkResult{ .comptimeExpr = self.comptimeExprs.items.len };
- try self.comptimeExprs.append(self.arena, .{
+ const res = DocData.WalkResult{ .comptimeExpr = self.comptime_exprs.items.len };
+ try self.comptime_exprs.append(self.arena, .{
.code = "if(banana) 1 else 0",
.typeRef = .{ .type = 0 },
});
@@ -669,7 +679,7 @@ fn walkInstruction(
// TODO: Actually, this is a good moment to check if
// the result is indeed a type!!
.comptimeExpr => {
- self.comptimeExprs.items[operand.comptimeExpr].typeRef = dest_type_ref;
+ self.comptime_exprs.items[operand.comptimeExpr].typeRef = dest_type_ref;
},
.int => operand.int.typeRef = dest_type_ref,
.@"struct" => operand.@"struct".typeRef = dest_type_ref,
@@ -731,35 +741,7 @@ fn walkInstruction(
// the analyzed data corresponding to the top-most decl of this path.
// We are now going to reverse loop over `path` to resolve each name
// to its corresponding index in `decls`.
-
- var i: usize = path.items.len;
- while (i > 1) {
- i -= 1;
- const parent = self.decls.items[path.items[i]];
- const child_decl_name = file.zir.nullTerminatedString(path.items[i - 1]);
- switch (parent.value) {
- else => {
- std.debug.print(
- "TODO: handle `{s}`in walkInstruction.field_val\n",
- .{@tagName(parent.value)},
- );
- unreachable;
- },
- .type => |t_index| {
- const t_struct = self.types.items[t_index].Struct; // todo: support more types
- for (t_struct.pubDecls) |d| {
- // TODO: this could be improved a lot
- // by having our own string table!
- const decl = self.decls.items[d];
- if (std.mem.eql(u8, decl.name, child_decl_name)) {
- path.items[i - 1] = d;
- continue;
- }
- }
- },
- }
- }
-
+ try self.tryResolveDeclPath(file, path.items);
return DocData.WalkResult{ .declPath = path.items };
},
.int_type => {
@@ -841,6 +823,29 @@ fn walkInstruction(
return DocData.WalkResult{ .type = self.types.items.len - 1 };
},
.extended => {
+ // TODO: this assumes that we always return a type when analyzing
+ // an extended instruction. Also we willingfully not reserve
+ // a slot for functions (handled right above) despite them
+ // being stored in `types`. The reason why we reserve a slot
+ // in here, is for decl paths and their resolution system.
+ const type_slot_index = self.types.items.len;
+ try self.types.append(self.arena, .{ .Unanalyzed = {} });
+
+ defer {
+ if (self.decl_paths_pending_on_types.get(type_slot_index)) |paths| {
+ for (paths.items) |resume_info| {
+ self.tryResolveDeclPath(resume_info.file, resume_info.path) catch {
+ @panic("Out of memory");
+ };
+ }
+
+ _ = self.decl_paths_pending_on_types.remove(type_slot_index);
+ // TODO: we should deallocate the arraylist that holds all the
+ // decl paths. not doing it now since it's arena-allocated
+ // anyway, but maybe we should put it elsewhere.
+ }
+ }
+
const extended = data[inst_index].extended;
switch (extended.opcode) {
else => {
@@ -898,6 +903,9 @@ fn walkInstruction(
{
var it = file.zir.declIterator(@intCast(u32, inst_index));
try self.decls.resize(self.arena, decls_first_index + it.decls_len);
+ for (self.decls.items[decls_first_index..]) |*slot| {
+ slot._analyzed = false;
+ }
var decls_slot_index = decls_first_index;
while (it.next()) |d| : (decls_slot_index += 1) {
const decl_name_index = file.zir.extra[d.sub_index + 5];
@@ -937,7 +945,7 @@ fn walkInstruction(
self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
- try self.types.append(self.arena, .{
+ self.types.items[type_slot_index] = .{
.Union = .{
.name = "todo_name",
.src = self_ast_node_index,
@@ -945,9 +953,9 @@ fn walkInstruction(
.pubDecls = decl_indexes.items,
.fields = field_type_refs.items,
},
- });
+ };
- return DocData.WalkResult{ .type = self.types.items.len - 1 };
+ return DocData.WalkResult{ .type = type_slot_index };
},
.enum_decl => {
var scope: Scope = .{ .parent = parent_scope };
@@ -998,6 +1006,9 @@ fn walkInstruction(
{
var it = file.zir.declIterator(@intCast(u32, inst_index));
try self.decls.resize(self.arena, decls_first_index + it.decls_len);
+ for (self.decls.items[decls_first_index..]) |*slot| {
+ slot._analyzed = false;
+ }
var decls_slot_index = decls_first_index;
while (it.next()) |d| : (decls_slot_index += 1) {
const decl_name_index = file.zir.extra[d.sub_index + 5];
@@ -1063,16 +1074,16 @@ fn walkInstruction(
self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
- try self.types.append(self.arena, .{
+ self.types.items[type_slot_index] = .{
.Enum = .{
.name = "todo_name",
.src = self_ast_node_index,
.privDecls = priv_decl_indexes.items,
.pubDecls = decl_indexes.items,
},
- });
+ };
- return DocData.WalkResult{ .type = self.types.items.len - 1 };
+ return DocData.WalkResult{ .type = type_slot_index };
},
.struct_decl => {
var scope: Scope = .{ .parent = parent_scope };
@@ -1116,6 +1127,9 @@ fn walkInstruction(
{
var it = file.zir.declIterator(@intCast(u32, inst_index));
try self.decls.resize(self.arena, decls_first_index + it.decls_len);
+ for (self.decls.items[decls_first_index..]) |*slot| {
+ slot._analyzed = false;
+ }
var decls_slot_index = decls_first_index;
while (it.next()) |d| : (decls_slot_index += 1) {
const decl_name_index = file.zir.extra[d.sub_index + 5];
@@ -1149,7 +1163,7 @@ fn walkInstruction(
self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
- try self.types.append(self.arena, .{
+ self.types.items[type_slot_index] = .{
.Struct = .{
.name = "todo_name",
.src = self_ast_node_index,
@@ -1157,9 +1171,9 @@ fn walkInstruction(
.pubDecls = decl_indexes.items,
.fields = field_type_refs.items,
},
- });
+ };
- return DocData.WalkResult{ .type = self.types.items.len - 1 };
+ return DocData.WalkResult{ .type = type_slot_index };
},
}
},
@@ -1310,6 +1324,7 @@ fn walkDecls(
};
self.decls.items[decl_being_tested].decltest = ast_node_index;
self.decls.items[decls_slot_index] = .{
+ ._analyzed = true,
.name = "test",
.src = ast_node_index,
.value = .{ .type = 0 },
@@ -1368,17 +1383,110 @@ fn walkDecls(
// };
self.decls.items[decls_slot_index] = .{
+ ._analyzed = true,
.name = name,
.src = ast_node_index,
// .typeRef = decl_type_ref,
.value = walk_result,
.kind = "const", // find where this information can be found
};
+
+ // Unblock any pending decl path that was waiting for this decl.
+ if (self.decl_paths_pending_on_decls.get(decls_slot_index)) |paths| {
+ for (paths.items) |resume_info| {
+ try self.tryResolveDeclPath(resume_info.file, resume_info.path);
+ }
+
+ _ = self.decl_paths_pending_on_decls.remove(decls_slot_index);
+ // TODO: we should deallocate the arraylist that holds all the
+ // decl paths. not doing it now since it's arena-allocated
+ // anyway, but maybe we should put it elsewhere.
+ }
}
return extra_index;
}
+/// 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.
+fn tryResolveDeclPath(
+ self: *Autodoc,
+ /// File from which the decl path originates.
+ file: *File,
+ path: []usize,
+) !void {
+ var i: usize = path.len;
+ while (i > 1) {
+ i -= 1;
+ const decl_index = path[i];
+ const string_index = path[i - 1];
+
+ const parent = self.decls.items[decl_index];
+ if (!parent._analyzed) {
+ const res = try self.decl_paths_pending_on_decls.getOrPut(self.arena, decl_index);
+ if (!res.found_existing) res.value_ptr.* = .{};
+ try res.value_ptr.*.append(self.arena, .{
+ .file = file,
+ .path = path[0 .. i + 1],
+ });
+ return;
+ }
+
+ const child_decl_name = file.zir.nullTerminatedString(string_index);
+ switch (parent.value) {
+ else => {
+ std.debug.panic(
+ "TODO: handle `{s}`in walkInstruction.field_val\n",
+ .{@tagName(parent.value)},
+ );
+ },
+ .type => |t_index| switch (self.types.items[t_index]) {
+ else => {
+ std.debug.panic(
+ "TODO: handle `{s}` in tryResolveDeclPath.type\n",
+ .{@tagName(self.types.items[t_index])},
+ );
+ },
+ .Unanalyzed => {
+ const res = try self.decl_paths_pending_on_types.getOrPut(
+ self.arena,
+ t_index,
+ );
+ if (!res.found_existing) res.value_ptr.* = .{};
+ try res.value_ptr.*.append(self.arena, .{
+ .file = file,
+ .path = path[0 .. i + 1],
+ });
+ return;
+ },
+ .Struct => |t_struct| {
+ for (t_struct.pubDecls) |d| {
+ // TODO: this could be improved a lot
+ // by having our own string table!
+ const decl = self.decls.items[d];
+ if (std.mem.eql(u8, decl.name, child_decl_name)) {
+ path[i - 1] = d;
+ continue;
+ }
+ }
+ for (t_struct.privDecls) |d| {
+ // TODO: this could be improved a lot
+ // by having our own string table!
+ const decl = self.decls.items[d];
+ if (std.mem.eql(u8, decl.name, child_decl_name)) {
+ path[i - 1] = d;
+ continue;
+ }
+ }
+ },
+ },
+ }
+ }
+}
+
fn collectUnionFieldInfo(
self: *Autodoc,
file: *File,