From 602029bb2fb78048e46136784e717b57b8de8f2c Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Wed, 12 Apr 2023 03:14:02 +0200 Subject: [PATCH] Autodoc usingnamespace (#15216) * autodoc: init support for usingnamespace decls * autodoc: don't build autodoc when building zig2.c * autodoc: usingnamespace decls support in frontend (#15203) * autodoc: init support for usingnamespace decls * autodoc: usingnamespace decls support in frontend --------- Co-authored-by: Krzysztof Wolicki <46651553+der-teufel-programming@users.noreply.github.com> --- lib/docs/main.js | 87 +++- src/Autodoc.zig | 974 ++++++++++++++++++++++++++------------------ src/Compilation.zig | 2 +- src/Zir.zig | 2 + 4 files changed, 641 insertions(+), 424 deletions(-) diff --git a/lib/docs/main.js b/lib/docs/main.js index 538fcf5229..7a94dc80fd 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -2586,7 +2586,8 @@ const NAV_MODES = { fnsList, varsList, valsList, - testsList + testsList, + unsList ) { for (let i = 0; i < decls.length; i += 1) { let decl = getDecl(decls[i]); @@ -2644,6 +2645,10 @@ const NAV_MODES = { valsList.push(decl); } } + + if (decl.is_uns) { + unsList.push(decl); + } } } @@ -2669,6 +2674,8 @@ const NAV_MODES = { let testsList = []; + let unsList = []; + categorizeDecls( container.pubDecls, typesList, @@ -2677,7 +2684,8 @@ const NAV_MODES = { fnsList, varsList, valsList, - testsList + testsList, + unsList ); if (curNav.showPrivDecls) categorizeDecls( @@ -2688,9 +2696,40 @@ const NAV_MODES = { fnsList, varsList, valsList, - testsList + testsList, + unsList ); + while (unsList.length > 0) { + let uns = unsList.shift(); + let declValue = resolveValue(uns.value); + if (!("type" in declValue.expr)) continue; + let uns_container = getType(declValue.expr.type); + categorizeDecls( + uns_container.pubDecls, + typesList, + namespacesList, + errSetsList, + fnsList, + varsList, + valsList, + testsList, + unsList + ); + if (curNav.showPrivDecls) + categorizeDecls( + uns_container.privDecls, + typesList, + namespacesList, + errSetsList, + fnsList, + varsList, + valsList, + testsList, + unsList + ); + } + typesList.sort(byNameProperty); namespacesList.sort(byNameProperty); errSetsList.sort(byNameProperty); @@ -3090,7 +3129,7 @@ const NAV_MODES = { function findSubDecl(parentTypeOrDecl, childName) { let parentType = parentTypeOrDecl; { - // Generic functions / resorlving decls + // Generic functions / resolving decls if ("value" in parentType) { const rv = resolveValue(parentType.value); if ("type" in rv.expr) { @@ -3116,20 +3155,35 @@ const NAV_MODES = { } } - if (!parentType.pubDecls) return null; - for (let i = 0; i < parentType.pubDecls.length; i += 1) { - let declIndex = parentType.pubDecls[i]; - let childDecl = getDecl(declIndex); - if (childDecl.name === childName) { - return childDecl; + if (parentType.pubDecls) { + for (let i = 0; i < parentType.pubDecls.length; i += 1) { + let declIndex = parentType.pubDecls[i]; + let childDecl = getDecl(declIndex); + if (childDecl.name === childName) { + return childDecl; + } else if (childDecl.is_uns) { + let declValue = resolveValue(childDecl.value); + if (!("type" in declValue.expr)) continue; + let uns_container = getType(declValue.expr.type); + let uns_res = findSubDecl(uns_container, childName); + if (uns_res !== null) return uns_res; + } } } - if (!parentType.privDecls) return null; - for (let i = 0; i < parentType.privDecls.length; i += 1) { - let declIndex = parentType.privDecls[i]; - let childDecl = getDecl(declIndex); - if (childDecl.name === childName) { - return childDecl; + + if (parentType.privDecls) { + for (let i = 0; i < parentType.privDecls.length; i += 1) { + let declIndex = parentType.privDecls[i]; + let childDecl = getDecl(declIndex); + if (childDecl.name === childName) { + return childDecl; + } else if (childDecl.is_uns) { + let declValue = resolveValue(childDecl.value); + if (!("type" in declValue.expr)) continue; + let uns_container = getType(declValue.expr.type); + let uns_res = findSubDecl(uns_container, childName); + if (uns_res !== null) return uns_res; + } } } return null; @@ -3908,6 +3962,7 @@ const NAV_MODES = { src: decl[2], value: decl[3], decltest: decl[4], + is_uns: decl[5], }; } diff --git a/src/Autodoc.zig b/src/Autodoc.zig index 5f1472a4ec..d3b654f9e0 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -37,7 +37,7 @@ pending_ref_paths: std.AutoHashMapUnmanaged( std.ArrayListUnmanaged(RefPathResumeInfo), ) = .{}, ref_paths_pending_on_decls: std.AutoHashMapUnmanaged( - usize, + *Scope.DeclStatus, std.ArrayListUnmanaged(RefPathResumeInfo), ) = .{}, ref_paths_pending_on_types: std.AutoHashMapUnmanaged( @@ -344,28 +344,48 @@ fn createFromPath(base_dir: std.fs.Dir, path: []const u8) !std.fs.File { } /// Represents a chain of scopes, used to resolve decl references to the -/// corresponding entry in `self.decls`. +/// corresponding entry in `self.decls`. It also keeps track of whether +/// a given decl has been analyzed or not. const Scope = struct { parent: ?*Scope, - map: std.AutoHashMapUnmanaged(u32, usize) = .{}, // index into `decls` + map: std.AutoHashMapUnmanaged( + u32, // index into the current file's string table (decl name) + DeclStatus, + ) = .{}, + enclosing_type: usize, // index into `types` - /// Assumes all decls in present scope and upper scopes have already - /// been either fully resolved or at least reserved. - pub fn resolveDeclName(self: Scope, string_table_idx: u32) usize { + pub const DeclStatus = union(enum) { + Analyzed: usize, // index into `decls` + Pending, + NotRequested: u32, // instr_index + + }; + + /// Returns a pointer so that the caller has a chance to modify the value + /// in case they decide to start analyzing a previously not requested decl. + pub fn resolveDeclName(self: Scope, string_table_idx: u32, file: *File, inst_index: usize) *DeclStatus { var cur: ?*const Scope = &self; return while (cur) |s| : (cur = s.parent) { - break s.map.get(string_table_idx) orelse continue; - } else unreachable; + break s.map.getPtr(string_table_idx) orelse continue; + } else { + printWithContext( + file, + inst_index, + "Could not find `{s}`\n\n", + .{file.zir.nullTerminatedString(string_table_idx)}, + ); + unreachable; + }; } pub fn insertDeclRef( self: *Scope, arena: std.mem.Allocator, - decl_name_index: u32, // decl name - decls_slot_index: usize, + decl_name_index: u32, // index into the current file's string table + decl_status: DeclStatus, ) !void { - try self.map.put(arena, decl_name_index, decls_slot_index); + try self.map.put(arena, decl_name_index, decl_status); } }; @@ -479,7 +499,7 @@ const DocData = struct { value: WalkResult, // The index in astNodes of the `test declname { }` node decltest: ?usize = null, - _analyzed: bool, // omitted in json data + is_uns: bool = false, // usingnamespace pub fn jsonStringify( self: Decl, @@ -676,7 +696,8 @@ const DocData = struct { @"&": usize, // index in `exprs` type: usize, // index in `types` this: usize, // index in `types` - declRef: usize, // index in `decls` + declRef: *Scope.DeclStatus, + declIndex: usize, // index into `decls`, alternative repr for `declRef` builtinField: enum { len, ptr }, fieldRef: FieldRef, refPath: []Expr, @@ -775,7 +796,11 @@ const DocData = struct { var jsw = std.json.writeStream(w, 15); if (opts.whitespace) |ws| jsw.whitespace = ws; try jsw.beginObject(); - try jsw.objectField(@tagName(active_tag)); + if (active_tag == .declIndex) { + try jsw.objectField("declRef"); + } else { + try jsw.objectField(@tagName(active_tag)); + } switch (self) { .int => { if (self.int.negated) try w.writeAll("-"); @@ -784,11 +809,16 @@ const DocData = struct { .builtinField => { try jsw.emitString(@tagName(self.builtinField)); }, + .declRef => { + try jsw.emitNumber(self.declRef.Analyzed); + }, else => { inline for (comptime std.meta.fields(Expr)) |case| { // TODO: this is super ugly, fix once `inline else` is a thing if (comptime std.mem.eql(u8, case.name, "builtinField")) continue; + if (comptime std.mem.eql(u8, case.name, "declRef")) + continue; if (@field(Expr, case.name) == active_tag) { try std.json.stringify(@field(self, case.name), opts, w); jsw.state_index -= 1; @@ -1133,7 +1163,7 @@ fn walkInstruction( self.exprs.items[slice_index] = .{ .slice = .{ .lhs = lhs_index, .start = start_index } }; return DocData.WalkResult{ - .typeRef = self.decls.items[lhs.expr.declRef].value.typeRef, + .typeRef = self.decls.items[lhs.expr.declRef.Analyzed].value.typeRef, .expr = .{ .sliceIndex = slice_index }, }; }, @@ -1175,7 +1205,7 @@ fn walkInstruction( self.exprs.items[slice_index] = .{ .slice = .{ .lhs = lhs_index, .start = start_index, .end = end_index } }; return DocData.WalkResult{ - .typeRef = self.decls.items[lhs.expr.declRef].value.typeRef, + .typeRef = self.decls.items[lhs.expr.declRef.Analyzed].value.typeRef, .expr = .{ .sliceIndex = slice_index }, }; }, @@ -1226,7 +1256,7 @@ fn walkInstruction( self.exprs.items[slice_index] = .{ .slice = .{ .lhs = lhs_index, .start = start_index, .end = end_index, .sentinel = sentinel_index } }; return DocData.WalkResult{ - .typeRef = self.decls.items[lhs.expr.declRef].value.typeRef, + .typeRef = self.decls.items[lhs.expr.declRef.Analyzed].value.typeRef, .expr = .{ .sliceIndex = slice_index }, }; }, @@ -1993,12 +2023,9 @@ fn walkInstruction( }, .decl_val, .decl_ref => { const str_tok = data[inst_index].str_tok; - const decls_slot_index = parent_scope.resolveDeclName(str_tok.start); - // While it would make sense to grab the original decl's typeRef info, - // that decl might not have been analyzed yet! The frontend will have - // to navigate through all declRefs to find the underlying type. + const decl_status = parent_scope.resolveDeclName(str_tok.start, file, inst_index); return DocData.WalkResult{ - .expr = .{ .declRef = decls_slot_index }, + .expr = .{ .declRef = decl_status }, }; }, .field_val, .field_call_bind, .field_ptr, .field_type => { @@ -2430,49 +2457,16 @@ fn walkInstruction( else parent_src; - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - var decl_indexes: std.ArrayListUnmanaged(usize) = .{}; var priv_decl_indexes: std.ArrayListUnmanaged(usize) = .{}; - const decls_first_index = self.decls.items.len; - // Decl name lookahead for reserving slots in `scope` (and `decls`). - // Done to make sure that all decl refs can be resolved correctly, - // even if we haven't fully analyzed the decl yet. - { - var it = file.zir.declIterator(@intCast(u32, inst_index)); - while (it.next()) |d| { - const decl_name_index = file.zir.extra[d.sub_index + 5]; - switch (decl_name_index) { - 0, 1, 2 => continue, - else => if (file.zir.string_bytes[decl_name_index] == 0) { - continue; - }, - } - - const decl_slot_index = self.decls.items.len; - try self.decls.append(self.arena, undefined); - self.decls.items[decl_slot_index]._analyzed = false; - - // TODO: inspect usingnamespace decls and unpack their contents! - - try scope.insertDeclRef(self.arena, decl_name_index, decl_slot_index); - } - } - - extra_index = try self.walkDecls( + extra_index = try self.analyzeAllDecls( file, &scope, + inst_index, src_info, - decls_first_index, - decls_len, &decl_indexes, &priv_decl_indexes, - extra_index, ); self.types.items[type_slot_index] = .{ @@ -2549,13 +2543,14 @@ fn walkInstruction( else parent_src; - const tag_type: ?DocData.Expr = if (small.has_tag_type) blk: { + // We delay analysis because union tags can refer to + // decls defined inside the union itself. + const tag_type_ref: Ref = if (small.has_tag_type) blk: { const tag_type = file.zir.extra[extra_index]; extra_index += 1; const tag_ref = @intToEnum(Ref, tag_type); - const wr = try self.walkRef(file, parent_scope, parent_src, tag_ref, false); - break :blk wr.expr; - } else null; + break :blk tag_ref; + } else .none; const body_len = if (small.has_body_len) blk: { const body_len = file.zir.extra[extra_index]; @@ -2569,51 +2564,28 @@ fn walkInstruction( break :blk fields_len; } else 0; - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - var decl_indexes: std.ArrayListUnmanaged(usize) = .{}; var priv_decl_indexes: std.ArrayListUnmanaged(usize) = .{}; - const decls_first_index = self.decls.items.len; - // Decl name lookahead for reserving slots in `scope` (and `decls`). - // Done to make sure that all decl refs can be resolved correctly, - // even if we haven't fully analyzed the decl yet. - { - var it = file.zir.declIterator(@intCast(u32, inst_index)); - while (it.next()) |d| { - const decl_name_index = file.zir.extra[d.sub_index + 5]; - switch (decl_name_index) { - 0, 1, 2 => continue, - else => if (file.zir.string_bytes[decl_name_index] == 0) { - continue; - }, - } - - const decl_slot_index = self.decls.items.len; - try self.decls.append(self.arena, undefined); - self.decls.items[decl_slot_index]._analyzed = false; - - // TODO: inspect usingnamespace decls and unpack their contents! - - try scope.insertDeclRef(self.arena, decl_name_index, decl_slot_index); - } - } - - extra_index = try self.walkDecls( + extra_index = try self.analyzeAllDecls( file, &scope, + inst_index, src_info, - decls_first_index, - decls_len, &decl_indexes, &priv_decl_indexes, - extra_index, ); + // Analyze the tag once all decls have been analyzed + const tag_type = try self.walkRef( + file, + &scope, + parent_src, + tag_type_ref, + false, + ); + + // Fields extra_index += body_len; var field_type_refs = try std.ArrayListUnmanaged(DocData.Expr).initCapacity( @@ -2643,7 +2615,7 @@ fn walkInstruction( .privDecls = priv_decl_indexes.items, .pubDecls = decl_indexes.items, .fields = field_type_refs.items, - .tag = tag_type, + .tag = tag_type.expr, .auto_enum = small.auto_enum_tag, }, }; @@ -2711,49 +2683,16 @@ fn walkInstruction( break :blk fields_len; } else 0; - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - var decl_indexes: std.ArrayListUnmanaged(usize) = .{}; var priv_decl_indexes: std.ArrayListUnmanaged(usize) = .{}; - const decls_first_index = self.decls.items.len; - // Decl name lookahead for reserving slots in `scope` (and `decls`). - // Done to make sure that all decl refs can be resolved correctly, - // even if we haven't fully analyzed the decl yet. - { - var it = file.zir.declIterator(@intCast(u32, inst_index)); - while (it.next()) |d| { - const decl_name_index = file.zir.extra[d.sub_index + 5]; - switch (decl_name_index) { - 0, 1, 2 => continue, - else => if (file.zir.string_bytes[decl_name_index] == 0) { - continue; - }, - } - - const decl_slot_index = self.decls.items.len; - try self.decls.append(self.arena, undefined); - self.decls.items[decl_slot_index]._analyzed = false; - - // TODO: inspect usingnamespace decls and unpack their contents! - - try scope.insertDeclRef(self.arena, decl_name_index, decl_slot_index); - } - } - - extra_index = try self.walkDecls( + extra_index = try self.analyzeAllDecls( file, &scope, + inst_index, src_info, - decls_first_index, - decls_len, &decl_indexes, &priv_decl_indexes, - extra_index, ); // const body = file.zir.extra[extra_index..][0..body_len]; @@ -2862,12 +2801,6 @@ fn walkInstruction( break :blk fields_len; } else 0; - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - // TODO: Expose explicit backing integer types in some way. if (small.has_backing_int) { const backing_int_body_len = file.zir.extra[extra_index]; @@ -2882,40 +2815,13 @@ fn walkInstruction( var decl_indexes: std.ArrayListUnmanaged(usize) = .{}; var priv_decl_indexes: std.ArrayListUnmanaged(usize) = .{}; - const decls_first_index = self.decls.items.len; - // Decl name lookahead for reserving slots in `scope` (and `decls`). - // Done to make sure that all decl refs can be resolved correctly, - // even if we haven't fully analyzed the decl yet. - { - var it = file.zir.declIterator(@intCast(u32, inst_index)); - while (it.next()) |d| { - const decl_name_index = file.zir.extra[d.sub_index + 5]; - switch (decl_name_index) { - 0, 1, 2 => continue, - else => if (file.zir.string_bytes[decl_name_index] == 0) { - continue; - }, - } - - const decl_slot_index = self.decls.items.len; - try self.decls.append(self.arena, undefined); - self.decls.items[decl_slot_index]._analyzed = false; - - // TODO: inspect usingnamespace decls and unpack their contents! - - try scope.insertDeclRef(self.arena, decl_name_index, decl_slot_index); - } - } - - extra_index = try self.walkDecls( + extra_index = try self.analyzeAllDecls( file, &scope, + inst_index, src_info, - decls_first_index, - decls_len, &decl_indexes, &priv_decl_indexes, - extra_index, ); var field_type_refs: std.ArrayListUnmanaged(DocData.Expr) = .{}; @@ -3096,189 +3002,286 @@ fn 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 this function. -fn walkDecls( +fn analyzeAllDecls( + self: *Autodoc, + file: *File, + scope: *Scope, + parent_inst_index: usize, + parent_src: SrcLocInfo, + decl_indexes: *std.ArrayListUnmanaged(usize), + priv_decl_indexes: *std.ArrayListUnmanaged(usize), +) AutodocErrors!usize { + const first_decl_indexes_slot = decl_indexes.items.len; + const original_it = file.zir.declIterator(@intCast(u32, parent_inst_index)); + + // First loop to discover decl names + { + var it = original_it; + while (it.next()) |d| { + const decl_name_index = file.zir.extra[d.sub_index + 5]; + switch (decl_name_index) { + 0, 1, 2 => continue, + else => if (file.zir.string_bytes[decl_name_index] == 0) { + continue; + }, + } + + try scope.insertDeclRef(self.arena, decl_name_index, .Pending); + } + } + + // Second loop to analyze `usingnamespace` decls + { + var it = original_it; + var decl_indexes_slot = first_decl_indexes_slot; + while (it.next()) |d| : (decl_indexes_slot += 1) { + const decl_name_index = file.zir.extra[d.sub_index + 5]; + switch (decl_name_index) { + 0 => { + const is_exported = @truncate(u1, d.flags >> 1); + switch (is_exported) { + 0 => continue, // comptime decl + 1 => { + try self.analyzeUsingnamespaceDecl( + file, + scope, + parent_src, + decl_indexes, + priv_decl_indexes, + d, + ); + }, + } + }, + else => continue, + } + } + } + + // Third loop to analyze all remaining decls + var it = original_it; + while (it.next()) |d| { + const decl_name_index = file.zir.extra[d.sub_index + 5]; + switch (decl_name_index) { + 0, 1, 2 => continue, // skip over usingnamespace decls + else => if (file.zir.string_bytes[decl_name_index] == 0) { + continue; + }, + } + + try self.analyzeDecl( + file, + scope, + parent_src, + decl_indexes, + priv_decl_indexes, + d, + ); + } + + return it.extra_index; +} + +// Asserts the given decl is public +fn analyzeDecl( self: *Autodoc, file: *File, scope: *Scope, parent_src: SrcLocInfo, - decls_first_index: usize, - decls_len: usize, decl_indexes: *std.ArrayListUnmanaged(usize), priv_decl_indexes: *std.ArrayListUnmanaged(usize), - extra_start: usize, -) AutodocErrors!usize { + d: Zir.DeclIterator.Item, +) AutodocErrors!void { const data = file.zir.instructions.items(.data); - const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable; - var extra_index = extra_start + bit_bags_count; - var bit_bag_index: usize = extra_start; - var cur_bit_bag: u32 = undefined; - var decl_i: u32 = 0; + const is_pub = @truncate(u1, d.flags >> 0) != 0; + // const is_exported = @truncate(u1, d.flags >> 1) != 0; + const has_align = @truncate(u1, d.flags >> 2) != 0; + const has_section_or_addrspace = @truncate(u1, d.flags >> 3) != 0; - // NOTE: we're not outputting every ZIR decl as a Autodoc decl. - // tests, comptime blocks and usingnamespace are skipped. - // this is why we `need good_decls_i`. - var good_decls_i: usize = 0; - while (decl_i < decls_len) : (decl_i += 1) { - const decls_slot_index = decls_first_index + good_decls_i; + var extra_index = d.sub_index; + // const hash_u32s = file.zir.extra[extra_index..][0..4]; - if (decl_i % 8 == 0) { - cur_bit_bag = file.zir.extra[bit_bag_index]; - bit_bag_index += 1; - } - const is_pub = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const is_exported = @truncate(u1, cur_bit_bag) != 0; - _ = is_exported; - cur_bit_bag >>= 1; - const has_align = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const has_section_or_addrspace = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; + extra_index += 4; + // const line = file.zir.extra[extra_index]; - // const sub_index = extra_index; + extra_index += 1; + const decl_name_index = file.zir.extra[extra_index]; - // const hash_u32s = file.zir.extra[extra_index..][0..4]; - extra_index += 4; + extra_index += 1; + const value_index = file.zir.extra[extra_index]; - // const line = file.zir.extra[extra_index]; + extra_index += 1; + const doc_comment_index = file.zir.extra[extra_index]; + + extra_index += 1; + const align_inst: Zir.Inst.Ref = if (!has_align) .none else inst: { + const inst = @intToEnum(Zir.Inst.Ref, file.zir.extra[extra_index]); extra_index += 1; - const decl_name_index = file.zir.extra[extra_index]; + break :inst inst; + }; + _ = align_inst; + + const section_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: { + const inst = @intToEnum(Zir.Inst.Ref, file.zir.extra[extra_index]); extra_index += 1; - const value_index = file.zir.extra[extra_index]; - extra_index += 1; - const doc_comment_index = file.zir.extra[extra_index]; + break :inst inst; + }; + _ = section_inst; + + const addrspace_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: { + const inst = @intToEnum(Zir.Inst.Ref, file.zir.extra[extra_index]); extra_index += 1; + break :inst inst; + }; + _ = addrspace_inst; - const align_inst: Zir.Inst.Ref = if (!has_align) .none else inst: { - const inst = @intToEnum(Zir.Inst.Ref, file.zir.extra[extra_index]); - extra_index += 1; - break :inst inst; - }; - _ = align_inst; + // This is known to work because decl values are always block_inlines + const value_pl_node = data[value_index].pl_node; + const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src); - const section_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: { - const inst = @intToEnum(Zir.Inst.Ref, file.zir.extra[extra_index]); - extra_index += 1; - break :inst inst; - }; - _ = section_inst; + const name: []const u8 = switch (decl_name_index) { + 0, 1 => unreachable, // comptime or usingnamespace decl + 2 => { + unreachable; + // decl test + // const decl_status = scope.resolveDeclName(doc_comment_index); + // const decl_being_tested = decl_status.Analyzed; + // const func_index = getBlockInlineBreak(file.zir, value_index).?; - const addrspace_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: { - const inst = @intToEnum(Zir.Inst.Ref, file.zir.extra[extra_index]); - extra_index += 1; - break :inst inst; - }; - _ = addrspace_inst; + // const pl_node = data[Zir.refToIndex(func_index).?].pl_node; + // const fn_src = try self.srcLocInfo(file, pl_node.src_node, decl_src); + // const tree = try file.getTree(self.module.gpa); + // const test_source_code = tree.getNodeSource(fn_src.src_node); - // This is known to work because decl values are always block_inlines - const value_pl_node = data[value_index].pl_node; - const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src); - - const name: []const u8 = switch (decl_name_index) { - 0, 1 => continue, // comptime or usingnamespace decl - 2 => { - // decl test - const decl_being_tested = scope.resolveDeclName(doc_comment_index); - const func_index = getBlockInlineBreak(file.zir, value_index).?; - - const pl_node = data[Zir.refToIndex(func_index).?].pl_node; - const fn_src = try self.srcLocInfo(file, pl_node.src_node, decl_src); - const tree = try file.getTree(self.module.gpa); - const test_source_code = tree.getNodeSource(fn_src.src_node); - - const ast_node_index = self.ast_nodes.items.len; - try self.ast_nodes.append(self.arena, .{ - .file = 0, - .line = 0, - .col = 0, - .code = test_source_code, - }); - self.decls.items[decl_being_tested].decltest = ast_node_index; - continue; - }, - else => blk: { - if (file.zir.string_bytes[decl_name_index] == 0) { - // test decl - continue; - } - break :blk file.zir.nullTerminatedString(decl_name_index); - }, - }; - - // If we got here, it means that this decl is not a test, usingnamespace - // or a comptime block decl. - good_decls_i += 1; - - const doc_comment: ?[]const u8 = if (doc_comment_index != 0) - file.zir.nullTerminatedString(doc_comment_index) - else - null; - - // astnode - const ast_node_index = idx: { - const idx = self.ast_nodes.items.len; - try self.ast_nodes.append(self.arena, .{ - .file = self.files.getIndex(file).?, - .line = decl_src.line, - .col = 0, - .docs = doc_comment, - .fields = null, // walkInstruction will fill `fields` if necessary - }); - break :idx idx; - }; - - const walk_result = try self.walkInstruction(file, scope, decl_src, value_index, true); - - if (is_pub) { - try decl_indexes.append(self.arena, decls_slot_index); - } else { - try priv_decl_indexes.append(self.arena, decls_slot_index); - } - - // // decl.typeRef == decl.val...typeRef - // const decl_type_ref: DocData.TypeRef = switch (walk_result) { - // .int => |i| i.typeRef, - // .void => .{ .type = @enumToInt(Ref.void_type) }, - // .@"undefined", .@"null" => |v| v, - // .@"unreachable" => .{ .type = @enumToInt(Ref.noreturn_type) }, - // .@"struct" => |s| s.typeRef, - // .bool => .{ .type = @enumToInt(Ref.bool_type) }, - // .type => .{ .type = @enumToInt(Ref.type_type) }, - // // this last case is special becauese it's not pointing - // // at the type of the value, but rather at the value itself - // // the js better be aware ot this! - // .declRef => |d| .{ .declRef = d }, - // }; - - const kind: []const u8 = if (try self.declIsVar(file, value_pl_node.src_node, parent_src)) "var" else "const"; - - self.decls.items[decls_slot_index] = .{ - ._analyzed = true, - .name = name, - .src = ast_node_index, - //.typeRef = decl_type_ref, - .value = walk_result, - .kind = kind, - }; - - // Unblock any pending decl path that was waiting for this decl. - if (self.ref_paths_pending_on_decls.get(decls_slot_index)) |paths| { - for (paths.items) |resume_info| { - try self.tryResolveRefPath( - resume_info.file, - value_index, - resume_info.ref_path, - ); + // const ast_node_index = self.ast_nodes.items.len; + // try self.ast_nodes.append(self.arena, .{ + // .file = 0, + // .line = 0, + // .col = 0, + // .code = test_source_code, + // }); + // self.decls.items[decl_being_tested].decltest = ast_node_index; + // continue; + }, + else => blk: { + if (file.zir.string_bytes[decl_name_index] == 0) { + // test decl + unreachable; } + break :blk file.zir.nullTerminatedString(decl_name_index); + }, + }; - _ = self.ref_paths_pending_on_decls.remove(decls_slot_index); - // TODO: we should deallocate the arraylist that holds all the - // ref paths. not doing it now since it's arena-allocated - // anyway, but maybe we should put it elsewhere. - } + const doc_comment: ?[]const u8 = if (doc_comment_index != 0) + file.zir.nullTerminatedString(doc_comment_index) + else + null; + + // astnode + const ast_node_index = idx: { + const idx = self.ast_nodes.items.len; + try self.ast_nodes.append(self.arena, .{ + .file = self.files.getIndex(file).?, + .line = decl_src.line, + .col = 0, + .docs = doc_comment, + .fields = null, // walkInstruction will fill `fields` if necessary + }); + break :idx idx; + }; + + const walk_result = try self.walkInstruction(file, scope, decl_src, value_index, true); + + const kind: []const u8 = if (try self.declIsVar(file, value_pl_node.src_node, parent_src)) "var" else "const"; + + const decls_slot_index = self.decls.items.len; + try self.decls.append(self.arena, .{ + .name = name, + .src = ast_node_index, + .value = walk_result, + .kind = kind, + }); + + if (is_pub) { + try decl_indexes.append(self.arena, decls_slot_index); + } else { + try priv_decl_indexes.append(self.arena, decls_slot_index); } - return extra_index; + const decl_status_ptr = scope.resolveDeclName(decl_name_index, file, 0); + std.debug.assert(decl_status_ptr.* == .Pending); + decl_status_ptr.* = .{ .Analyzed = decls_slot_index }; + + // Unblock any pending decl path that was waiting for this decl. + if (self.ref_paths_pending_on_decls.get(decl_status_ptr)) |paths| { + for (paths.items) |resume_info| { + try self.tryResolveRefPath( + resume_info.file, + value_index, + resume_info.ref_path, + ); + } + + _ = self.ref_paths_pending_on_decls.remove(decl_status_ptr); + // TODO: we should deallocate the arraylist that holds all the + // ref paths. not doing it now since it's arena-allocated + // anyway, but maybe we should put it elsewhere. + } +} + +fn analyzeUsingnamespaceDecl( + self: *Autodoc, + file: *File, + scope: *Scope, + parent_src: SrcLocInfo, + decl_indexes: *std.ArrayListUnmanaged(usize), + priv_decl_indexes: *std.ArrayListUnmanaged(usize), + d: Zir.DeclIterator.Item, +) AutodocErrors!void { + const data = file.zir.instructions.items(.data); + + const is_pub = @truncate(u1, d.flags) != 0; + const value_index = file.zir.extra[d.sub_index + 6]; + const doc_comment_index = file.zir.extra[d.sub_index + 7]; + + // This is known to work because decl values are always block_inlines + const value_pl_node = data[value_index].pl_node; + const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src); + + const doc_comment: ?[]const u8 = if (doc_comment_index != 0) + file.zir.nullTerminatedString(doc_comment_index) + else + null; + + // astnode + const ast_node_index = idx: { + const idx = self.ast_nodes.items.len; + try self.ast_nodes.append(self.arena, .{ + .file = self.files.getIndex(file).?, + .line = decl_src.line, + .col = 0, + .docs = doc_comment, + .fields = null, // walkInstruction will fill `fields` if necessary + }); + break :idx idx; + }; + + const walk_result = try self.walkInstruction(file, scope, decl_src, value_index, true); + + const decl_slot_index = self.decls.items.len; + try self.decls.append(self.arena, .{ + .name = "", + .kind = "", + .src = ast_node_index, + .value = walk_result, + .is_uns = true, + }); + + if (is_pub) { + try decl_indexes.append(self.arena, decl_slot_index); + } else { + try priv_decl_indexes.append(self.arena, decl_slot_index); + } } /// An unresolved path has a non-string WalkResult at its beginnig, while every @@ -3290,7 +3293,7 @@ fn walkDecls( /// Same happens when a decl holds a type definition that hasn't been fully /// analyzed yet (except that we append to `self.ref_paths_pending_on_types`. /// -/// When walkDecls / walkInstruction finishes analyzing a decl / type, it will +/// When analyzeAllDecls / walkInstruction finishes analyzing a decl / type, it will /// then check if there's any pending ref path blocked on it and, if any, it /// will progress their resolution by calling tryResolveRefPath again. /// @@ -3318,37 +3321,51 @@ fn tryResolveRefPath( switch (resolved_parent) { else => break, .this => |t| resolved_parent = .{ .type = t }, - .declRef => |decl_index| { + .declIndex => |decl_index| { const decl = self.decls.items[decl_index]; - if (decl._analyzed) { - resolved_parent = decl.value.expr; - continue; + resolved_parent = decl.value.expr; + continue; + }, + .declRef => |decl_status_ptr| { + // NOTE: must be kep in sync with `findNameInUnsDecls` + switch (decl_status_ptr.*) { + // The use of unreachable here is conservative. + // It might be that it truly should be up to us to + // request the analys of this decl, but it's not clear + // at the moment of writing. + .NotRequested => unreachable, + .Analyzed => |decl_index| { + const decl = self.decls.items[decl_index]; + resolved_parent = decl.value.expr; + continue; + }, + .Pending => { + // This decl path is pending completion + { + const res = try self.pending_ref_paths.getOrPut( + self.arena, + &path[path.len - 1], + ); + if (!res.found_existing) res.value_ptr.* = .{}; + } + + const res = try self.ref_paths_pending_on_decls.getOrPut( + self.arena, + decl_status_ptr, + ); + if (!res.found_existing) res.value_ptr.* = .{}; + try res.value_ptr.*.append(self.arena, .{ + .file = file, + .ref_path = path[i..path.len], + }); + + // We return instead doing `break :outer` to prevent the + // code after the :outer while loop to run, as it assumes + // that the path will have been fully analyzed (or we + // have given up because of a comptimeExpr). + return; + }, } - - // This decl path is pending completion - { - const res = try self.pending_ref_paths.getOrPut( - self.arena, - &path[path.len - 1], - ); - if (!res.found_existing) res.value_ptr.* = .{}; - } - - const res = try self.ref_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, - .ref_path = path[i..path.len], - }); - - // We return instead doing `break :outer` to prevent the - // code after the :outer while loop to run, as it assumes - // that the path will have been fully analyzed (or we - // have given up because of a comptimeExpr). - return; }, .refPath => |rp| { if (self.pending_ref_paths.getPtr(&rp[rp.len - 1])) |waiter_list| { @@ -3388,7 +3405,7 @@ fn tryResolveRefPath( panicWithContext( file, inst_index, - "exhausted eval quota for `{}`in tryResolveDecl\n", + "exhausted eval quota for `{}`in tryResolveRefPath\n", .{resolved_parent}, ); } @@ -3461,26 +3478,39 @@ fn tryResolveRefPath( ); } }, - .Enum => |t_enum| { - for (t_enum.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_string)) { - path[i + 1] = .{ .declRef = d }; + // TODO: the following searches could probably + // be performed more efficiently on the corresponding + // scope + .Enum => |t_enum| { // foo.bar.baz + // Look into locally-defined pub decls + for (t_enum.pubDecls) |idx| { + const d = self.decls.items[idx]; + if (d.is_uns) continue; + if (std.mem.eql(u8, d.name, child_string)) { + path[i + 1] = .{ .declIndex = idx }; continue :outer; } } - for (t_enum.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_string)) { - path[i + 1] = .{ .declRef = d }; + + // Look into locally-defined priv decls + for (t_enum.privDecls) |idx| { + const d = self.decls.items[idx]; + if (d.is_uns) continue; + if (std.mem.eql(u8, d.name, child_string)) { + path[i + 1] = .{ .declIndex = idx }; continue :outer; } } + switch (try self.findNameInUnsDecls(file, path[i..path.len], resolved_parent, child_string)) { + .Pending => return, + .NotFound => {}, + .Found => |match| { + path[i + 1] = match; + continue :outer; + }, + } + for (self.ast_nodes.items[t_enum.src].fields.?, 0..) |ast_node, idx| { const name = self.ast_nodes.items[ast_node].name.?; if (std.mem.eql(u8, name, child_string)) { @@ -3509,25 +3539,35 @@ fn tryResolveRefPath( continue :outer; }, .Union => |t_union| { - for (t_union.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_string)) { - path[i + 1] = .{ .declRef = d }; + // Look into locally-defined pub decls + for (t_union.pubDecls) |idx| { + const d = self.decls.items[idx]; + if (d.is_uns) continue; + if (std.mem.eql(u8, d.name, child_string)) { + path[i + 1] = .{ .declIndex = idx }; continue :outer; } } - for (t_union.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_string)) { - path[i + 1] = .{ .declRef = d }; + + // Look into locally-defined priv decls + for (t_union.privDecls) |idx| { + const d = self.decls.items[idx]; + if (d.is_uns) continue; + if (std.mem.eql(u8, d.name, child_string)) { + path[i + 1] = .{ .declIndex = idx }; continue :outer; } } + switch (try self.findNameInUnsDecls(file, path[i..path.len], resolved_parent, child_string)) { + .Pending => return, + .NotFound => {}, + .Found => |match| { + path[i + 1] = match; + continue :outer; + }, + } + for (self.ast_nodes.items[t_union.src].fields.?, 0..) |ast_node, idx| { const name = self.ast_nodes.items[ast_node].name.?; if (std.mem.eql(u8, name, child_string)) { @@ -3556,25 +3596,35 @@ fn tryResolveRefPath( }, .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_string)) { - path[i + 1] = .{ .declRef = d }; + // Look into locally-defined pub decls + for (t_struct.pubDecls) |idx| { + const d = self.decls.items[idx]; + if (d.is_uns) continue; + if (std.mem.eql(u8, d.name, child_string)) { + path[i + 1] = .{ .declIndex = idx }; continue :outer; } } - 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_string)) { - path[i + 1] = .{ .declRef = d }; + + // Look into locally-defined priv decls + for (t_struct.privDecls) |idx| { + const d = self.decls.items[idx]; + if (d.is_uns) continue; + if (std.mem.eql(u8, d.name, child_string)) { + path[i + 1] = .{ .declIndex = idx }; continue :outer; } } + switch (try self.findNameInUnsDecls(file, path[i..path.len], resolved_parent, child_string)) { + .Pending => return, + .NotFound => {}, + .Found => |match| { + path[i + 1] = match; + continue :outer; + }, + } + for (self.ast_nodes.items[t_struct.src].fields.?, 0..) |ast_node, idx| { const name = self.ast_nodes.items[ast_node].name.?; if (std.mem.eql(u8, name, child_string)) { @@ -3605,25 +3655,37 @@ fn tryResolveRefPath( continue :outer; }, .Opaque => |t_opaque| { - for (t_opaque.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_string)) { - path[i + 1] = .{ .declRef = d }; + // Look into locally-defined pub decls + for (t_opaque.pubDecls) |idx| { + const d = self.decls.items[idx]; + if (d.is_uns) continue; + if (std.mem.eql(u8, d.name, child_string)) { + path[i + 1] = .{ .declIndex = idx }; continue :outer; } } - for (t_opaque.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_string)) { - path[i + 1] = .{ .declRef = d }; + + // Look into locally-defined priv decls + for (t_opaque.privDecls) |idx| { + const d = self.decls.items[idx]; + if (d.is_uns) continue; + if (std.mem.eql(u8, d.name, child_string)) { + path[i + 1] = .{ .declIndex = idx }; continue :outer; } } + // We delay looking into Uns decls since they could be + // not fully analyzed yet. + switch (try self.findNameInUnsDecls(file, path[i..path.len], resolved_parent, child_string)) { + .Pending => return, + .NotFound => {}, + .Found => |match| { + path[i + 1] = match; + continue :outer; + }, + } + // if we got here, our search failed printWithContext( file, @@ -3670,6 +3732,104 @@ fn tryResolveRefPath( // that said, we might want to store it elsewhere and reclaim memory asap } } + +const UnsSearchResult = union(enum) { + Found: DocData.Expr, + Pending, + NotFound, +}; + +fn findNameInUnsDecls( + self: *Autodoc, + file: *File, + tail: []DocData.Expr, + uns_expr: DocData.Expr, + name: []const u8, +) !UnsSearchResult { + var to_analyze = std.SegmentedList(DocData.Expr, 1){}; + // TODO: make this an appendAssumeCapacity + try to_analyze.append(self.arena, uns_expr); + + while (to_analyze.pop()) |cte| { + var container_expression = cte; + for (0..10_000) |_| { + // TODO: handle other types of indirection, like @import + const type_index = switch (container_expression) { + .type => |t| t, + .declRef => |decl_status_ptr| { + switch (decl_status_ptr.*) { + // The use of unreachable here is conservative. + // It might be that it truly should be up to us to + // request the analys of this decl, but it's not clear + // at the moment of writing. + .NotRequested => unreachable, + .Analyzed => |decl_index| { + const decl = self.decls.items[decl_index]; + container_expression = decl.value.expr; + continue; + }, + .Pending => { + // This decl path is pending completion + { + const res = try self.pending_ref_paths.getOrPut( + self.arena, + &tail[tail.len - 1], + ); + if (!res.found_existing) res.value_ptr.* = .{}; + } + + const res = try self.ref_paths_pending_on_decls.getOrPut( + self.arena, + decl_status_ptr, + ); + if (!res.found_existing) res.value_ptr.* = .{}; + try res.value_ptr.*.append(self.arena, .{ + .file = file, + .ref_path = tail, + }); + + // TODO: save some state that keeps track of our + // progress because, as things stand, we + // always re-start the search from scratch + return .Pending; + }, + } + }, + else => { + log.debug( + "Handle `{s}` in findNameInUnsDecls (first switch)", + .{@tagName(cte)}, + ); + return .{ .Found = .{ .comptimeExpr = 0 } }; + }, + }; + + const t = self.types.items[type_index]; + const decls = switch (t) { + else => { + log.debug( + "Handle `{s}` in findNameInUnsDecls (second switch)", + .{@tagName(cte)}, + ); + return .{ .Found = .{ .comptimeExpr = 0 } }; + }, + inline .Struct, .Union, .Opaque, .Enum => |c| c.pubDecls, + }; + + for (decls) |idx| { + const d = self.decls.items[idx]; + if (d.is_uns) { + try to_analyze.append(self.arena, d.value.expr); + } else if (std.mem.eql(u8, d.name, name)) { + return .{ .Found = .{ .declIndex = idx } }; + } + } + } + } + + return .NotFound; +} + fn analyzeFancyFunction( self: *Autodoc, file: *File, diff --git a/src/Compilation.zig b/src/Compilation.zig index 6eef76cf14..4c3e49fb52 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2052,7 +2052,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void return; } - if (!build_options.only_c) { + if (!build_options.only_c and !build_options.omit_pkg_fetching_code) { if (comp.emit_docs) |doc_location| { if (comp.bin_file.options.module) |module| { var autodoc = Autodoc.init(module, doc_location); diff --git a/src/Zir.zig b/src/Zir.zig index 7a8df49fda..eb90165f00 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -3667,6 +3667,7 @@ pub const DeclIterator = struct { pub const Item = struct { name: [:0]const u8, sub_index: u32, + flags: u4, }; pub fn next(it: *DeclIterator) ?Item { @@ -3691,6 +3692,7 @@ pub const DeclIterator = struct { return Item{ .sub_index = sub_index, .name = name, + .flags = flags, }; } };