Commit 602029bb2f

Loris Cro <kappaloris@gmail.com>
2023-04-12 03:14:02
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>
1 parent 52d552f
Changed files (4)
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],
     };
   }
 
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,
-    decls_first_index: usize,
-    decls_len: usize,
     decl_indexes: *std.ArrayListUnmanaged(usize),
     priv_decl_indexes: *std.ArrayListUnmanaged(usize),
-    extra_start: usize,
 ) AutodocErrors!usize {
-    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 first_decl_indexes_slot = decl_indexes.items.len;
+    const original_it = file.zir.declIterator(@intCast(u32, parent_inst_index));
 
-    // 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;
+    // 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;
+                },
+            }
 
-        if (decl_i % 8 == 0) {
-            cur_bit_bag = file.zir.extra[bit_bag_index];
-            bit_bag_index += 1;
+            try scope.insertDeclRef(self.arena, decl_name_index, .Pending);
         }
-        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;
+    }
 
-        // const sub_index = extra_index;
+    // 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,
+            }
+        }
+    }
 
-        // const hash_u32s = file.zir.extra[extra_index..][0..4];
-        extra_index += 4;
+    // 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;
+            },
+        }
 
-        // const line = file.zir.extra[extra_index];
-        extra_index += 1;
-        const decl_name_index = 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];
-        extra_index += 1;
+        try self.analyzeDecl(
+            file,
+            scope,
+            parent_src,
+            decl_indexes,
+            priv_decl_indexes,
+            d,
+        );
+    }
 
-        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;
+    return it.extra_index;
+}
 
-        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;
+// Asserts the given decl is public
+fn analyzeDecl(
+    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) != 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;
 
-        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;
+    var extra_index = d.sub_index;
+    // const hash_u32s = file.zir.extra[extra_index..][0..4];
 
-        // 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);
+    extra_index += 4;
+    // const line = file.zir.extra[extra_index];
 
-        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).?;
+    extra_index += 1;
+    const decl_name_index = file.zir.extra[extra_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);
+    extra_index += 1;
+    const value_index = file.zir.extra[extra_index];
 
-                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);
-            },
-        };
+    extra_index += 1;
+    const doc_comment_index = file.zir.extra[extra_index];
 
-        // If we got here, it means that this decl is not a test, usingnamespace
-        // or a comptime block decl.
-        good_decls_i += 1;
+    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;
+        break :inst inst;
+    };
+    _ = align_inst;
 
-        const doc_comment: ?[]const u8 = if (doc_comment_index != 0)
-            file.zir.nullTerminatedString(doc_comment_index)
-        else
-            null;
+    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;
 
-        // 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 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;
+
+    // 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 => 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 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
+                unreachable;
+            }
+            break :blk file.zir.nullTerminatedString(decl_name_index);
+        },
+    };
 
-        const walk_result = try self.walkInstruction(file, scope, decl_src, value_index, true);
+    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;
+    };
 
-        if (is_pub) {
-            try decl_indexes.append(self.arena, decls_slot_index);
-        } else {
-            try priv_decl_indexes.append(self.arena, decls_slot_index);
-        }
+    const walk_result = try self.walkInstruction(file, scope, decl_src, value_index, true);
 
-        // // 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,
-        };
+    const kind: []const u8 = if (try self.declIsVar(file, value_pl_node.src_node, parent_src)) "var" else "const";
 
-        // 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 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);
+    }
 
-            _ = 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 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.
     }
+}
 
-    return extra_index;
+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;
-                    }
-
-                    // 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.* = .{};
-                    }
+                    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_index,
-                    );
-                    if (!res.found_existing) res.value_ptr.* = .{};
-                    try res.value_ptr.*.append(self.arena, .{
-                        .file = file,
-                        .ref_path = path[i..path.len],
-                    });
+                            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;
+                            // 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,
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);
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,
         };
     }
 };