Commit 40a2844c30

Loris Cro <kappaloris@gmail.com>
2022-03-21 18:10:20
autodoc: decl paths become ref paths
originally I thought `foo.bar.baz` was a path of decls, but turns out other language constructs require to make this model more general. originally a decl path was an array of decl indexes, now it's an array of `WalkResult`s
1 parent 63be9e6
Changed files (1)
src/Autodoc.zig
@@ -22,22 +22,22 @@ comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},
 
 // These fields hold temporary state of the analysis process
 // and are mainly used by the decl path resolving algorithm.
-pending_decl_paths: std.AutoHashMapUnmanaged(
-    *usize, // pointer to declpath head (ie `&decl_path[0]`)
-    std.ArrayListUnmanaged(DeclPathResumeInfo),
+pending_ref_paths: std.AutoHashMapUnmanaged(
+    *DocData.WalkResult, // pointer to declpath tail end (ie `&decl_path[decl_path.len - 1]`)
+    std.ArrayListUnmanaged(RefPathResumeInfo),
 ) = .{},
-decl_paths_pending_on_decls: std.AutoHashMapUnmanaged(
+ref_paths_pending_on_decls: std.AutoHashMapUnmanaged(
     usize,
-    std.ArrayListUnmanaged(DeclPathResumeInfo),
+    std.ArrayListUnmanaged(RefPathResumeInfo),
 ) = .{},
-decl_paths_pending_on_types: std.AutoHashMapUnmanaged(
+ref_paths_pending_on_types: std.AutoHashMapUnmanaged(
     usize,
-    std.ArrayListUnmanaged(DeclPathResumeInfo),
+    std.ArrayListUnmanaged(RefPathResumeInfo),
 ) = .{},
 
-const DeclPathResumeInfo = struct {
+const RefPathResumeInfo = struct {
     file: *File,
-    decl_path: DocData.DeclPath,
+    ref_path: []DocData.WalkResult,
 };
 
 var arena_allocator: std.heap.ArenaAllocator = undefined;
@@ -79,6 +79,8 @@ pub fn generateZirData(self: *Autodoc) !void {
         try self.types.append(self.arena, .{
             .ComptimeExpr = .{ .name = "ComptimeExpr" },
         });
+
+        var tr = DocData.WalkResult{ .type = @enumToInt(Ref.usize_type) };
         // this skipts Ref.none but it's ok becuse we replaced it with ComptimeExpr
         var i: u32 = 1;
         while (i <= @enumToInt(Ref.anyerror_void_error_union_type)) : (i += 1) {
@@ -94,9 +96,7 @@ pub fn generateZirData(self: *Autodoc) !void {
                             .Array = .{
                                 .len = .{
                                     .int = .{
-                                        .typeRef = .{
-                                            .type = @enumToInt(Ref.usize_type),
-                                        },
+                                        .typeRef = &tr,
                                         .value = 1,
                                         .negated = false,
                                     },
@@ -165,15 +165,15 @@ pub fn generateZirData(self: *Autodoc) !void {
     try self.files.put(self.arena, file, main_type_index);
     _ = try self.walkInstruction(file, &root_scope, Zir.main_struct_inst);
 
-    if (self.decl_paths_pending_on_decls.count() > 0) {
+    if (self.ref_paths_pending_on_decls.count() > 0) {
         @panic("some decl paths were never fully analized (pending on decls)");
     }
 
-    if (self.decl_paths_pending_on_types.count() > 0) {
+    if (self.ref_paths_pending_on_types.count() > 0) {
         @panic("some decl paths were never fully analized (pending on types)");
     }
 
-    if (self.pending_decl_paths.count() > 0) {
+    if (self.pending_ref_paths.count() > 0) {
         @panic("some decl paths were never fully analized");
     }
 
@@ -304,7 +304,7 @@ const DocData = struct {
     decls: []Decl,
     comptimeExprs: []ComptimeExpr,
     const Call = struct {
-        func: TypeRef,
+        func: WalkResult,
         args: []WalkResult,
         ret: WalkResult,
     };
@@ -337,7 +337,7 @@ const DocData = struct {
 
     const ComptimeExpr = struct {
         code: []const u8,
-        typeRef: TypeRef,
+        typeRef: WalkResult,
     };
     const Package = struct {
         name: []const u8 = "root",
@@ -379,18 +379,18 @@ const DocData = struct {
         Float: struct { name: []const u8 },
         Pointer: struct {
             size: std.builtin.TypeInfo.Pointer.Size,
-            child: TypeRef,
+            child: WalkResult,
         },
         Array: struct {
             len: WalkResult,
-            child: TypeRef,
+            child: WalkResult,
         },
         Struct: struct {
             name: []const u8,
-            src: ?usize = null, // index into astNodes
+            src: usize, // index into astNodes
             privDecls: []usize = &.{}, // index into decls
             pubDecls: []usize = &.{}, // index into decls
-            fields: ?[]TypeRef = null, // (use src->fields to find names)
+            fields: ?[]WalkResult = null, // (use src->fields to find names)
         },
         ComptimeExpr: struct { name: []const u8 },
         ComptimeFloat: struct { name: []const u8 },
@@ -399,7 +399,7 @@ const DocData = struct {
         Null: struct { name: []const u8 },
         Optional: struct {
             name: []const u8,
-            child: TypeRef,
+            child: WalkResult,
         },
         ErrorUnion: struct { name: []const u8 },
         ErrorSet: struct {
@@ -418,13 +418,13 @@ const DocData = struct {
             src: ?usize = null, // index into astNodes
             privDecls: ?[]usize = null, // index into decls
             pubDecls: ?[]usize = null, // index into decls
-            fields: ?[]TypeRef = null, // (use src->fields to find names)
+            fields: ?[]WalkResult = null, // (use src->fields to find names)
         },
         Fn: struct {
             name: []const u8,
             src: ?usize = null, // index into astNodes
-            ret: TypeRef,
-            params: ?[]TypeRef = null, // (use src->fields to find names)
+            ret: WalkResult,
+            params: ?[]WalkResult = null, // (use src->fields to find names)
         },
         BoundFn: struct { name: []const u8 },
         Opaque: struct { name: []const u8 },
@@ -511,66 +511,6 @@ const DocData = struct {
         }
     };
 
-    /// A DeclPath represents an expression such as `foo.bar.baz` where each
-    /// component has been resolved to a corresponding index in `self.decls`.
-    /// If a DeclPath has a component that can't be fully solved (eg the
-    /// function call in `foo.bar().baz`), then it will be solved up until the
-    /// unresolved component, leaving the remaining part unresolved.
-    ///
-    /// Note that DeclPaths are currently stored in inverse order: the innermost
-    /// component is at index 0.
-    const DeclPath = struct {
-        path: []usize, // indexes in `decls`
-        hasCte: bool = false, // a prefix of this path could not be resolved
-        // TODO: make hasCte return the actual index where the cte is!
-    };
-
-    /// A TypeRef is a subset of WalkResult that refers a type in a direct or
-    /// indirect manner.
-    ///
-    /// An example of directness is `const foo = struct {...};`.
-    /// An example of indidirectness is `const bar = foo;`.
-    const TypeRef = union(enum) {
-        unspecified,
-        @"anytype",
-        declPath: DeclPath,
-        type: usize, // index in `types`
-        comptimeExpr: usize, // index in `comptimeExprs`
-        // TODO: maybe we should not consider calls to be typerefs and instread
-        // directly refer to their return value. The problem at the moment
-        // is that we can't analyze function calls at all.
-        call: usize, // index in `calls`
-        typeOf: *WalkResult,
-
-        pub fn jsonStringify(
-            self: TypeRef,
-            options: std.json.StringifyOptions,
-            w: anytype,
-        ) !void {
-            switch (self) {
-                .typeOf => |v| try std.json.stringify(v, options, w),
-                .unspecified, .@"anytype" => {
-                    try w.print(
-                        \\{{ "{s}":{{}} }}
-                    , .{@tagName(self)});
-                },
-
-                .type, .comptimeExpr, .call => |v| {
-                    try w.print(
-                        \\{{ "{s}":{} }}
-                    , .{ @tagName(self), v });
-                },
-                .declPath => |v| {
-                    try w.print("{{ \"hasCte\": {}, \"declPath\": [", .{v.hasCte});
-                    for (v.path) |d, i| {
-                        const comma = if (i == v.path.len - 1) "]}" else ",";
-                        try w.print("{d}{s}", .{ d, comma });
-                    }
-                },
-            }
-        }
-    };
-
     /// A WalkResult represents the result of the analysis process done to a
     /// declaration. This includes: decls, fields, etc.
     ///
@@ -581,20 +521,23 @@ const DocData = struct {
         comptimeExpr: usize, // index in `comptimeExprs`
         void,
         @"unreachable",
-        @"null": TypeRef,
-        @"undefined": TypeRef,
+        @"null": *WalkResult,
+        @"undefined": *WalkResult,
         @"struct": Struct,
         bool: bool,
         @"anytype",
         type: usize, // index in `types`
-        declPath: DeclPath,
+        this: usize, // index in `types`
+        declRef: usize, // index in `decls`
+        fieldRef: FieldRef,
+        refPath: []WalkResult,
         int: struct {
-            typeRef: TypeRef,
+            typeRef: *WalkResult,
             value: usize, // direct value
             negated: bool = false,
         },
         float: struct {
-            typeRef: TypeRef,
+            typeRef: *WalkResult,
             value: f64, // direct value
             negated: bool = false,
         },
@@ -606,8 +549,13 @@ const DocData = struct {
         compileError: []const u8,
         string: []const u8,
 
+        const FieldRef = struct {
+            type: usize, // index in `types`
+            index: usize, // index in type.fields
+        };
+
         const Struct = struct {
-            typeRef: TypeRef,
+            typeRef: *WalkResult,
             fieldVals: []FieldVal,
 
             const FieldVal = struct {
@@ -616,7 +564,7 @@ const DocData = struct {
             };
         };
         const Array = struct {
-            typeRef: TypeRef,
+            typeRef: *WalkResult,
             data: []WalkResult,
         };
 
@@ -624,14 +572,14 @@ const DocData = struct {
             self: WalkResult,
             options: std.json.StringifyOptions,
             w: anytype,
-        ) !void {
+        ) std.os.WriteError!void {
             switch (self) {
                 .void, .@"unreachable", .@"anytype" => {
                     try w.print(
                         \\{{ "{s}":{{}} }}
                     , .{@tagName(self)});
                 },
-                .type, .comptimeExpr, .call => |v| {
+                .type, .comptimeExpr, .call, .this, .declRef => |v| {
                     try w.print(
                         \\{{ "{s}":{} }}
                     , .{ @tagName(self), v });
@@ -666,16 +614,22 @@ const DocData = struct {
                 .typeOf, .sizeOf => |v| try std.json.stringify(v, options, w),
                 .compileError => |v| try std.json.stringify(v, options, w),
                 .string => |v| try std.json.stringify(v, options, w),
+                .fieldRef => |v| try std.json.stringify(
+                    struct { fieldRef: FieldRef }{ .fieldRef = v },
+                    options,
+                    w,
+                ),
                 .@"struct" => |v| try std.json.stringify(
                     struct { @"struct": Struct }{ .@"struct" = v },
                     options,
                     w,
                 ),
-                .declPath => |v| {
-                    try w.print("{{ \"hasCte\": {}, \"declPath\": [", .{v.hasCte});
-                    for (v.path) |d, i| {
-                        const comma = if (i == v.path.len - 1) "]}" else ",";
-                        try w.print("{d}{s}", .{ d, comma });
+                .refPath => |v| {
+                    try w.print("{{ \"refPath\": [", .{});
+                    for (v) |c, i| {
+                        const comma = if (i == v.len - 1) "]}" else ",\n";
+                        try c.jsonStringify(options, w);
+                        try w.print("{s}", .{comma});
                     }
                 },
                 .array => |v| try std.json.stringify(
@@ -758,6 +712,7 @@ fn walkInstruction(
                     .comptimeExpr = cte_slot_index,
                 };
             }
+
             const new_file = self.module.importFile(file, path) catch unreachable;
             const result = try self.files.getOrPut(self.arena, new_file.file);
             if (result.found_existing) {
@@ -794,37 +749,24 @@ fn walkInstruction(
 
             return DocData.WalkResult{ .compileError = operand.string };
         },
-        .switch_block => {
-            const cte_slot_index = self.comptime_exprs.items.len;
-            try self.comptime_exprs.append(self.arena, .{
-                .code = "switch",
-                .typeRef = .{
-                    .type = @enumToInt(DocData.DocTypeKinds.ComptimeExpr),
-                },
-            });
-
-            return DocData.WalkResult{ .comptimeExpr = cte_slot_index };
-        },
+        .switch_block => return self.cteTodo("[switch]"),
         .enum_literal => {
             const str_tok = data[inst_index].str_tok;
             const literal = file.zir.nullTerminatedString(str_tok.start);
             return DocData.WalkResult{ .enumLiteral = literal };
         },
-        .div_exact, .div => {
-            const cte_slot_index = self.comptime_exprs.items.len;
-            try self.comptime_exprs.append(self.arena, .{
-                .code = "@div*(...)",
-                .typeRef = .{ .type = @enumToInt(DocData.DocTypeKinds.ComptimeExpr) },
-            });
-            return DocData.WalkResult{ .comptimeExpr = cte_slot_index };
-        },
+        .div_exact, .div => return self.cteTodo("@div(...)"),
+        .mul => return self.cteTodo("@mul(...)"),
+        .array_mul => return self.cteTodo("a ** b"),
+        .bool_br_and, .bool_br_or => return self.cteTodo("bool op"),
+        .cmp_eq => return self.cteTodo("bool op"),
         .int => {
             const int = data[inst_index].int;
+            const t = try self.arena.create(DocData.WalkResult);
+            t.* = .{ .type = @enumToInt(Ref.comptime_int_type) };
             return DocData.WalkResult{
                 .int = .{
-                    .typeRef = .{
-                        .type = @enumToInt(Ref.comptime_int_type),
-                    },
+                    .typeRef = t,
                     .value = int,
                 },
             };
@@ -843,7 +785,7 @@ fn walkInstruction(
             try self.types.append(self.arena, .{
                 .Pointer = .{
                     .size = ptr.size,
-                    .child = walkResultToTypeRef(elem_type_ref),
+                    .child = elem_type_ref,
                 },
             });
 
@@ -862,7 +804,7 @@ fn walkInstruction(
             try self.types.append(self.arena, .{
                 .Pointer = .{
                     .size = ptr.size,
-                    .child = walkResultToTypeRef(elem_type_ref),
+                    .child = elem_type_ref,
                 },
             });
 
@@ -871,7 +813,7 @@ fn walkInstruction(
         .array_type => {
             const bin = data[inst_index].bin;
             const len = try self.walkRef(file, parent_scope, bin.lhs);
-            const child = walkResultToTypeRef(try self.walkRef(file, parent_scope, bin.rhs));
+            const child = try self.walkRef(file, parent_scope, bin.rhs);
 
             const type_slot_index = self.types.items.len;
             try self.types.append(self.arena, .{
@@ -891,12 +833,15 @@ fn walkInstruction(
                 array_data[idx] = try self.walkRef(file, parent_scope, op);
             }
 
+            const at = try self.arena.create(DocData.WalkResult);
+            at.* = .{ .type = @enumToInt(Ref.usize_type) };
+
             const type_slot_index = self.types.items.len;
             try self.types.append(self.arena, .{
                 .Array = .{
                     .len = .{
                         .int = .{
-                            .typeRef = .{ .type = @enumToInt(Ref.usize_type) },
+                            .typeRef = at,
                             .value = operands.len,
                             .negated = false,
                         },
@@ -905,18 +850,22 @@ fn walkInstruction(
                 },
             });
 
+            const t = try self.arena.create(DocData.WalkResult);
+            t.* = .{ .type = type_slot_index };
             return DocData.WalkResult{ .array = .{
-                .typeRef = .{ .type = type_slot_index },
+                .typeRef = t,
                 .data = array_data,
             } };
         },
         .float => {
             const float = data[inst_index].float;
+
+            const t = try self.arena.create(DocData.WalkResult);
+            t.* = .{ .type = @enumToInt(Ref.comptime_float_type) };
+
             return DocData.WalkResult{
                 .float = .{
-                    .typeRef = .{
-                        .type = @enumToInt(Ref.comptime_float_type),
-                    },
+                    .typeRef = t,
                     .value = float,
                 },
             };
@@ -956,7 +905,7 @@ fn walkInstruction(
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.As, pl_node.payload_index);
             const dest_type_walk = try self.walkRef(file, parent_scope, extra.data.dest_type);
-            const dest_type_ref = walkResultToTypeRef(dest_type_walk);
+            const dest_type_ref = dest_type_walk;
 
             var operand = try self.walkRef(file, parent_scope, extra.data.operand);
 
@@ -967,7 +916,7 @@ fn walkInstruction(
                     "TODO: handle {s} in `walkInstruction.as_node`\n",
                     .{@tagName(operand)},
                 ),
-                .declPath, .type, .string => {},
+                .refPath, .type, .string, .call, .enumLiteral => {},
                 // we don't do anything because up until now,
                 // I've only seen this used as such:
                 //       @as(@as(type, Baz), .{})
@@ -979,9 +928,9 @@ fn walkInstruction(
                 .comptimeExpr => {
                     self.comptime_exprs.items[operand.comptimeExpr].typeRef = dest_type_ref;
                 },
-                .int => operand.int.typeRef = dest_type_ref,
-                .@"struct" => operand.@"struct".typeRef = dest_type_ref,
-                .@"undefined" => operand.@"undefined" = dest_type_ref,
+                .int => operand.int.typeRef.* = dest_type_ref,
+                .@"struct" => operand.@"struct".typeRef.* = dest_type_ref,
+                .@"undefined" => operand.@"undefined".* = dest_type_ref,
             }
 
             return operand;
@@ -993,7 +942,7 @@ fn walkInstruction(
                 parent_scope,
                 un_node.operand,
             );
-            const type_ref = walkResultToTypeRef(operand);
+            const type_ref = operand;
             const res = DocData.WalkResult{ .type = self.types.items.len };
             try self.types.append(self.arena, .{
                 .Optional = .{ .name = "?TODO", .child = type_ref },
@@ -1003,9 +952,7 @@ fn walkInstruction(
         .decl_val, .decl_ref => {
             const str_tok = data[inst_index].str_tok;
             const decls_slot_index = parent_scope.resolveDeclName(str_tok.start);
-            var path = try self.arena.alloc(usize, 1);
-            path[0] = decls_slot_index;
-            return DocData.WalkResult{ .declPath = .{ .path = path } };
+            return DocData.WalkResult{ .declRef = decls_slot_index };
         },
         .field_val, .field_call_bind, .field_ptr, .field_type => {
             // TODO: field type uses Zir.Inst.FieldType, it just happens to have the
@@ -1013,101 +960,51 @@ fn walkInstruction(
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.Field, pl_node.payload_index);
 
-            var path: std.ArrayListUnmanaged(usize) = .{};
+            var path: std.ArrayListUnmanaged(DocData.WalkResult) = .{};
             var lhs = @enumToInt(extra.data.lhs) - Ref.typed_value_map.len; // underflow = need to handle Refs
 
-            try path.append(self.arena, extra.data.field_name_start);
+            try path.append(self.arena, .{
+                .string = file.zir.nullTerminatedString(extra.data.field_name_start),
+            });
             // Put inside path the starting index of each decl name that
             // we encounter as we navigate through all the field_vals
             while (tags[lhs] == .field_val or
                 tags[lhs] == .field_call_bind or
-                tags[lhs] == .field_ptr)
+                tags[lhs] == .field_ptr or
+                tags[lhs] == .field_type)
             {
                 const lhs_extra = file.zir.extraData(
                     Zir.Inst.Field,
                     data[lhs].pl_node.payload_index,
                 );
 
-                try path.append(self.arena, lhs_extra.data.field_name_start);
+                try path.append(self.arena, .{
+                    .string = file.zir.nullTerminatedString(lhs_extra.data.field_name_start),
+                });
                 lhs = @enumToInt(lhs_extra.data.lhs) - Ref.typed_value_map.len; // underflow = need to handle Refs
             }
 
-            switch (tags[lhs]) {
-                else => panicWithContext(
-                    file,
-                    inst_index,
-                    "TODO: handle `{s}` in walkInstruction.field_val",
-                    .{@tagName(tags[lhs])},
-                ),
-                .call => {
-                    const walk_result = try self.walkInstruction(file, parent_scope, lhs);
-                    const ast_node_index = idx: {
-                        const idx = self.ast_nodes.items.len;
-                        try self.ast_nodes.append(self.arena, .{
-                            .file = 0,
-                            .line = 0,
-                            .col = 0,
-                            .docs = "",
-                            .fields = null,
-                        });
-                        break :idx idx;
-                    };
-
-                    const decls_slot_index = self.decls.items.len;
-                    try self.decls.append(self.arena, .{
-                        ._analyzed = true,
-                        .name = "call()",
-                        .src = ast_node_index,
-                        .value = walk_result,
-                        .kind = "const",
-                    });
-                    try path.append(self.arena, decls_slot_index);
-                },
-                .import => {
-                    const walk_result = try self.walkInstruction(file, parent_scope, lhs);
-
-                    // astnode
-                    const ast_node_index = idx: {
-                        const idx = self.ast_nodes.items.len;
-                        try self.ast_nodes.append(self.arena, .{
-                            .file = 0,
-                            .line = 0,
-                            .col = 0,
-                            .docs = "",
-                            .fields = null,
-                        });
-                        break :idx idx;
-                    };
-                    const str_tok = data[lhs].str_tok;
-                    const file_path = str_tok.get(file.zir);
-
-                    const name = try std.fmt.allocPrint(self.arena, "@import({s})", .{file_path});
-                    const decls_slot_index = self.decls.items.len;
-                    try self.decls.append(self.arena, .{
-                        ._analyzed = true,
-                        .name = name,
-                        .src = ast_node_index,
-                        // .typeRef = decl_type_ref,
-                        .value = walk_result,
-                        .kind = "const", // find where this information can be found
-                    });
-                    try path.append(self.arena, decls_slot_index);
-                },
-                .decl_val, .decl_ref => {
-                    const str_tok = data[lhs].str_tok;
-                    const decls_slot_index = parent_scope.resolveDeclName(str_tok.start);
-                    try path.append(self.arena, decls_slot_index);
-                },
-            }
-
-            // Righ now, every element of `path` is the first index of a
-            // decl name except for the final element, which instead points to
-            // the analyzed data corresponding to the top-most decl of this path.
-            // We are now going to reverse loop over `path` to resolve each name
-            // to its corresponding index in `decls`.
-            var decl_path: DocData.DeclPath = .{ .path = path.items };
-            try self.tryResolveDeclPath(file, &decl_path);
-            return DocData.WalkResult{ .declPath = decl_path };
+            const wr = try self.walkInstruction(file, parent_scope, lhs);
+            try path.append(self.arena, wr);
+
+            // This way the data in `path` has the same ordering that the ref
+            // path has in the text: most general component first.
+            std.mem.reverse(DocData.WalkResult, path.items);
+
+            // Righ now, every element of `path` is a string except its first
+            // element (at index 0). We're now going to attempt to resolve each
+            // string. If one or more components in this path are not yet fully
+            // analyzed, the path will only be solved partially, but we expect
+            // to eventually solve it fully(or give up in case of a
+            // comptimeExpr). This means that:
+            // - (1) Paths can be not fully analyzed temporarily, so any code
+            //       that requires to know where a ref path leads to, neeeds to
+            //       implement support for lazyness (see self.pending_ref_paths)
+            // - (2) Paths can sometimes never resolve fully. This means that
+            //       any value that depends on that will have to become a
+            //       comptimeExpr.
+            try self.tryResolveRefPath(file, lhs, path.items);
+            return DocData.WalkResult{ .refPath = path.items };
         },
         .int_type => {
             const int_type = data[inst_index].int_type;
@@ -1139,7 +1036,7 @@ fn walkInstruction(
                 extra.data.fields_len,
             );
 
-            var type_ref: DocData.TypeRef = undefined;
+            const type_ref = try self.arena.create(DocData.WalkResult);
             var idx = extra.end;
             for (field_vals) |*fv| {
                 const init_extra = file.zir.extraData(Zir.Inst.StructInit.Item, idx);
@@ -1161,11 +1058,10 @@ fn walkInstruction(
                             parent_scope,
                             field_extra.data.container_type,
                         );
-                        type_ref = walkResultToTypeRef(wr);
+                        type_ref.* = wr;
                     }
                     break :blk file.zir.nullTerminatedString(field_extra.data.name_start);
                 };
-
                 const value = try self.walkRef(file, parent_scope, init_extra.data.init);
                 fv.* = .{ .name = field_name, .val = value };
             }
@@ -1236,9 +1132,7 @@ fn walkInstruction(
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.Call, pl_node.payload_index);
 
-            const callee = walkResultToTypeRef(
-                try self.walkRef(file, parent_scope, extra.data.callee),
-            );
+            const callee = try self.walkRef(file, parent_scope, extra.data.callee);
 
             const args_len = extra.data.flags.args_len;
             var args = try self.arena.alloc(DocData.WalkResult, args_len);
@@ -1267,16 +1161,20 @@ fn walkInstruction(
             return DocData.WalkResult{ .call = call_slot_index };
         },
         .func, .func_inferred => {
+            const type_slot_index = self.types.items.len;
+            try self.types.append(self.arena, .{ .Unanalyzed = {} });
+
             return self.analyzeFunction(
                 file,
                 parent_scope,
                 inst_index,
                 self_ast_node_index,
+                type_slot_index,
             );
         },
         .extended => {
             // NOTE: this code + the subsequent defer block are working towards
-            // solving pending decl paths that depend on a type to be analyzed.
+            // solving pending decl paths that depend on completing the analysis of a type.
             // When we don't find a type, the defer will run anyway but shouldn't
             // ever be able to find a match inside `decl_paths_pending_on_types`
             // TODO: extract this logic into a function and only call it when appropriate.
@@ -1284,14 +1182,18 @@ fn walkInstruction(
             try self.types.append(self.arena, .{ .Unanalyzed = {} });
 
             defer {
-                if (self.decl_paths_pending_on_types.get(type_slot_index)) |paths| {
-                    for (paths.items) |*resume_info| {
-                        self.tryResolveDeclPath(resume_info.file, &resume_info.decl_path) catch {
+                if (self.ref_paths_pending_on_types.get(type_slot_index)) |paths| {
+                    for (paths.items) |resume_info| {
+                        self.tryResolveRefPath(
+                            resume_info.file,
+                            inst_index,
+                            resume_info.ref_path,
+                        ) catch {
                             @panic("Out of memory");
                         };
                     }
 
-                    _ = self.decl_paths_pending_on_types.remove(type_slot_index);
+                    _ = self.ref_paths_pending_on_types.remove(type_slot_index);
                     // TODO: we should deallocate the arraylist that holds all the
                     //       decl paths. not doing it now since it's arena-allocated
                     //       anyway, but maybe we should put it elsewhere.
@@ -1308,12 +1210,15 @@ fn walkInstruction(
                         .{@tagName(extended.opcode)},
                     );
                 },
+
+                .opaque_decl => return self.cteTodo("opaque {...}"),
                 .func => {
                     return try self.analyzeFunction(
                         file,
                         parent_scope,
                         inst_index,
                         self_ast_node_index,
+                        type_slot_index,
                     );
                 },
                 .variable => {
@@ -1403,7 +1308,7 @@ fn walkInstruction(
                     // const body = file.zir.extra[extra_index..][0..body_len];
                     extra_index += body_len;
 
-                    var field_type_refs = try std.ArrayListUnmanaged(DocData.TypeRef).initCapacity(
+                    var field_type_refs = try std.ArrayListUnmanaged(DocData.WalkResult).initCapacity(
                         self.arena,
                         fields_len,
                     );
@@ -1633,7 +1538,7 @@ fn walkInstruction(
                     // const body = file.zir.extra[extra_index..][0..body_len];
                     extra_index += body_len;
 
-                    var field_type_refs: std.ArrayListUnmanaged(DocData.TypeRef) = .{};
+                    var field_type_refs: std.ArrayListUnmanaged(DocData.WalkResult) = .{};
                     var field_name_indexes: std.ArrayListUnmanaged(usize) = .{};
                     try self.collectStructFieldInfo(
                         file,
@@ -1659,22 +1564,7 @@ fn walkInstruction(
                     return DocData.WalkResult{ .type = type_slot_index };
                 },
                 .this => {
-                    // TODO: consider if we should reuse an existing decl
-                    //       that points to this type (if present).
-                    const decl_slot_index = self.decls.items.len;
-                    try self.decls.append(self.arena, .{
-                        .name = "@This()",
-                        .value = .{ .type = parent_scope.enclosing_type },
-                        .src = 0,
-                        .kind = "const",
-                        ._analyzed = false,
-                    });
-                    const dpath = try self.arena.alloc(usize, 1);
-                    dpath[0] = decl_slot_index;
-                    return DocData.WalkResult{ .declPath = .{
-                        .hasCte = false,
-                        .path = dpath,
-                    } };
+                    return DocData.WalkResult{ .this = parent_scope.enclosing_type };
                 },
             }
         },
@@ -1900,14 +1790,18 @@ fn walkDecls(
         };
 
         // Unblock any pending decl path that was waiting for this decl.
-        if (self.decl_paths_pending_on_decls.get(decls_slot_index)) |paths| {
-            for (paths.items) |*resume_info| {
-                try self.tryResolveDeclPath(resume_info.file, &resume_info.decl_path);
+        if (self.ref_paths_pending_on_decls.get(decls_slot_index)) |paths| {
+            for (paths.items) |resume_info| {
+                try self.tryResolveRefPath(
+                    resume_info.file,
+                    decl_index,
+                    resume_info.ref_path,
+                );
             }
 
-            _ = self.decl_paths_pending_on_decls.remove(decls_slot_index);
+            _ = self.ref_paths_pending_on_decls.remove(decls_slot_index);
             // TODO: we should deallocate the arraylist that holds all the
-            //       decl paths. not doing it now since it's arena-allocated
+            //       ref paths. not doing it now since it's arena-allocated
             //       anyway, but maybe we should put it elsewhere.
         }
     }
@@ -1915,109 +1809,139 @@ fn walkDecls(
     return extra_index;
 }
 
-/// An unresolved path has a decl index at its end, while every other element
-/// is an index into the string table. Resolving means iteratively map each
-/// string to a decl_index.
+/// An unresolved path has a non-string WalkResult at its beginnig, while every
+/// other element is a string WalkResult. Resolving means iteratively map each
+/// string to a Decl / Type / Call / etc.
 ///
 /// If we encounter an unanalyzed decl during the process, we append the
-/// unsolved sub-path to `self.decl_paths_pending_on_decls` and bail out.
+/// unsolved sub-path to `self.ref_paths_pending_on_decls` and bail out.
 /// Same happens when a decl holds a type definition that hasn't been fully
-/// analyzed yet (except that we append to `self.decl_paths_pending_on_types`.
+/// analyzed yet (except that we append to `self.ref_paths_pending_on_types`.
 ///
-/// When a decl or a type is fully analyzed if will then check if there's any
-/// pending decl path blocked on it and, if any, will progress their resolution
-/// by calling tryResolveDeclPath again.
+/// When walkDecls / 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.
 ///
-/// Decl paths can also depend on other decl paths. See
-/// `self.pending_decl_paths` for more info.
+/// Ref paths can also depend on other ref paths. See
+/// `self.pending_ref_paths` for more info.
 ///
-/// A decl path that has a component that resolves into a comptimeExpr will
-/// give up its resolution process entirely.
-///
-/// TODO: when giving up, translate remaining string indexes into data that
-///       can be used by the frontend. Requires implementing a frontend string
-///       table.
-fn tryResolveDeclPath(
+/// A ref path that has a component that resolves into a comptimeExpr will
+/// give up its resolution process entirely, leaving the remaining components
+/// as strings.
+fn tryResolveRefPath(
     self: *Autodoc,
     /// File from which the decl path originates.
     file: *File,
-    decl_path: *DocData.DeclPath,
+    inst_index: usize, // used only for panicWithContext
+    path: []DocData.WalkResult,
 ) error{OutOfMemory}!void {
-    const path: []usize = decl_path.path;
+    var i: usize = 0;
+    outer: while (i < path.len - 1) : (i += 1) {
+        const parent = path[i];
+        const child_string = path[i + 1].string; // we expect to find a string union case
+
+        var resolved_parent = parent;
+        var j: usize = 0;
+        while (j < 10_000) : (j += 1) {
+            switch (resolved_parent) {
+                else => break,
+                .declRef => |decl_index| {
+                    const decl = self.decls.items[decl_index];
+                    if (decl._analyzed) {
+                        resolved_parent = decl.value;
+                        continue;
+                    }
 
-    var i: usize = path.len;
-    outer: while (i > 1) {
-        i -= 1;
-        const decl_index = path[i];
-        const string_index = path[i - 1];
+                    // 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 parent = self.decls.items[decl_index];
-        if (!parent._analyzed) {
-            // This decl path is pending completion
-            {
-                const res = try self.pending_decl_paths.getOrPut(self.arena, &path[0]);
-                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.decl_paths_pending_on_decls.getOrPut(self.arena, decl_index);
-            if (!res.found_existing) res.value_ptr.* = .{};
-            try res.value_ptr.*.append(self.arena, .{
-                .file = file,
-                .decl_path = .{ .path = path[0 .. i + 1] },
-            });
+                    // 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| {
+                        try waiter_list.append(self.arena, .{
+                            .file = file,
+                            .ref_path = path[i..path.len],
+                        });
 
-            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.* = .{};
+                        }
+
+                        return;
+                    }
+
+                    // If the last element is a string or a CTE, then we give up,
+                    // otherwise we resovle the parent to it and loop again.
+                    // NOTE: we assume that if we find a string, it's because of
+                    // a CTE component somewhere in the path. We know that the path
+                    // is not pending futher evaluation because we just checked!
+                    const last = rp[rp.len - 1];
+                    switch (last) {
+                        .comptimeExpr, .string => break :outer,
+                        else => {
+                            resolved_parent = last;
+                            continue;
+                        },
+                    }
+                },
+            }
+        } else {
+            panicWithContext(
+                file,
+                inst_index,
+                "exhausted eval quota for `{}`in tryResolveDecl\n",
+                .{resolved_parent},
+            );
         }
 
-        const child_decl_name = file.zir.nullTerminatedString(string_index);
-        switch (parent.value) {
+        switch (resolved_parent) {
             else => {
-                std.debug.panic(
-                    "TODO: handle `{s}`in tryResolveDecl\n \"{s}\":{}",
-                    .{ @tagName(parent.value), parent.name, parent.value },
+                // NOTE: indirect references to types / decls should be handled
+                //       in the switch above this one!
+                panicWithContext(
+                    file,
+                    inst_index,
+                    "TODO: handle `{s}`in tryResolveRefPath\nInfo: {}",
+                    .{ @tagName(resolved_parent), resolved_parent },
                 );
             },
             .comptimeExpr, .call => {
                 // Since we hit a cte, we leave the remaining strings unresolved
                 // and completely give up on resolving this decl path.
-                decl_path.hasCte = true;
+                //decl_path.hasCte = true;
                 break :outer;
             },
-            .declPath => |dp| {
-                if (dp.hasCte) {
-                    decl_path.hasCte = true;
-                    break :outer;
-                }
-                if (self.pending_decl_paths.getPtr(&dp.path[0])) |waiter_list| {
-                    try waiter_list.append(self.arena, .{
-                        .file = file,
-                        .decl_path = .{ .path = path[0 .. i + 1] },
-                    });
-
-                    // This decl path is pending completion
-                    {
-                        const res = try self.pending_decl_paths.getOrPut(self.arena, &path[0]);
-                        if (!res.found_existing) res.value_ptr.* = .{};
-                    }
-
-                    return;
-                }
-
-                const final_decl_index = dp.path[0];
-                // For the purpose of being able to call tryResolveDeclPath again,
-                // we momentarily replace the decl index present in `path[i]`
-                // with the final decl in `dp`.
-                // We then write the original value back as soon as we're done with the
-                // recoursive call. This will work out correctly even if the path
-                // will not get fully resolved (also in the case that final_decl is
-                // not resolved yet).
-                path[i] = final_decl_index;
-                try self.tryResolveDeclPath(file, decl_path);
-                path[i] = decl_index;
-            },
             .type => |t_index| switch (self.types.items[t_index]) {
                 else => {
-                    std.debug.panic(
+                    panicWithContext(
+                        file,
+                        inst_index,
                         "TODO: handle `{s}` in tryResolveDeclPath.type\n",
                         .{@tagName(self.types.items[t_index])},
                     );
@@ -2025,53 +1949,87 @@ fn tryResolveDeclPath(
                 .Unanalyzed => {
                     // This decl path is pending completion
                     {
-                        const res = try self.pending_decl_paths.getOrPut(self.arena, &path[0]);
+                        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.decl_paths_pending_on_types.getOrPut(
+                    const res = try self.ref_paths_pending_on_types.getOrPut(
                         self.arena,
                         t_index,
                     );
                     if (!res.found_existing) res.value_ptr.* = .{};
                     try res.value_ptr.*.append(self.arena, .{
                         .file = file,
-                        .decl_path = .{ .path = path[0 .. i + 1] },
+                        .ref_path = path[i..path.len],
                     });
 
                     return;
                 },
                 .Struct => |t_struct| {
+                    std.debug.print("search: {s}\n", .{child_string});
                     for (t_struct.pubDecls) |d| {
                         // TODO: this could be improved a lot
                         //       by having our own string table!
                         const decl = self.decls.items[d];
-                        if (std.mem.eql(u8, decl.name, child_decl_name)) {
-                            path[i - 1] = d;
-                            continue;
+                        std.debug.print("pub decl `{s}`\n", .{decl.name});
+                        if (std.mem.eql(u8, decl.name, child_string)) {
+                            std.debug.print("match!\n", .{});
+                            path[i + 1] = .{ .declRef = d };
+                            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_decl_name)) {
-                            path[i - 1] = d;
-                            continue;
+                        std.debug.print("priv decl `{s}`\n", .{decl.name});
+                        if (std.mem.eql(u8, decl.name, child_string)) {
+                            std.debug.print("match!\n", .{});
+                            path[i + 1] = .{ .declRef = d };
+                            continue :outer;
+                        }
+                    }
+
+                    for (self.ast_nodes.items[t_struct.src].fields.?) |ast_node, idx| {
+                        const name = self.ast_nodes.items[ast_node].name.?;
+                        std.debug.print("field `{s}`\n", .{name});
+                        if (std.mem.eql(u8, name, child_string)) {
+                            std.debug.print("match!\n", .{});
+                            // TODO: should we really create an artificial
+                            //       decl for this type? Probably not.
+
+                            path[i + 1] = .{
+                                .fieldRef = .{
+                                    .type = t_index,
+                                    .index = idx,
+                                },
+                            };
+                            continue :outer;
                         }
                     }
+
+                    // if we got here, our search failed
+                    panicWithContext(
+                        file,
+                        inst_index,
+                        "failed to match `{s}`",
+                        .{child_string},
+                    );
                 },
             },
         }
     }
 
-    if (self.pending_decl_paths.get(&path[0])) |waiter_list| {
+    if (self.pending_ref_paths.get(&path[path.len - 1])) |waiter_list| {
         // It's important to de-register oureslves as pending before
         // attempting to resolve any other decl.
-        _ = self.pending_decl_paths.remove(&path[0]);
+        _ = self.pending_ref_paths.remove(&path[path.len - 1]);
 
-        for (waiter_list.items) |*resume_info| {
-            try self.tryResolveDeclPath(resume_info.file, &resume_info.decl_path);
+        for (waiter_list.items) |resume_info| {
+            try self.tryResolveRefPath(resume_info.file, inst_index, resume_info.ref_path);
         }
         // TODO: this is where we should free waiter_list, but its in the arena
         //       that said, we might want to store it elsewhere and reclaim memory asap
@@ -2084,13 +2042,14 @@ fn analyzeFunction(
     scope: *Scope,
     inst_index: usize,
     self_ast_node_index: usize,
+    type_slot_index: usize,
 ) error{OutOfMemory}!DocData.WalkResult {
     const tags = file.zir.instructions.items(.tag);
     const data = file.zir.instructions.items(.data);
-
     const fn_info = file.zir.getFnInfo(@intCast(u32, inst_index));
+
     try self.ast_nodes.ensureUnusedCapacity(self.arena, fn_info.total_params_len);
-    var param_type_refs = try std.ArrayListUnmanaged(DocData.TypeRef).initCapacity(
+    var param_type_refs = try std.ArrayListUnmanaged(DocData.WalkResult).initCapacity(
         self.arena,
         fn_info.total_params_len,
     );
@@ -2098,6 +2057,7 @@ fn analyzeFunction(
         self.arena,
         fn_info.total_params_len,
     );
+
     // TODO: handle scope rules for fn parameters
     for (fn_info.param_body[0..fn_info.total_params_len]) |param_index| {
         switch (tags[param_index]) {
@@ -2121,7 +2081,7 @@ fn analyzeFunction(
                 });
 
                 param_type_refs.appendAssumeCapacity(
-                    DocData.TypeRef{ .@"anytype" = {} },
+                    DocData.WalkResult{ .@"anytype" = {} },
                 );
             },
             .param, .param_comptime => {
@@ -2144,9 +2104,7 @@ fn analyzeFunction(
                 const break_operand = data[break_index].@"break".operand;
                 const param_type_ref = try self.walkRef(file, scope, break_operand);
 
-                param_type_refs.appendAssumeCapacity(
-                    walkResultToTypeRef(param_type_ref),
-                );
+                param_type_refs.appendAssumeCapacity(param_type_ref);
             },
         }
     }
@@ -2156,18 +2114,18 @@ fn analyzeFunction(
         const last_instr_index = fn_info.ret_ty_body[fn_info.ret_ty_body.len - 1];
         const break_operand = data[last_instr_index].@"break".operand;
         const wr = try self.walkRef(file, scope, break_operand);
-        break :blk walkResultToTypeRef(wr);
+        break :blk wr;
     };
 
     self.ast_nodes.items[self_ast_node_index].fields = param_ast_indexes.items;
-    try self.types.append(self.arena, .{
+    self.types.items[type_slot_index] = .{
         .Fn = .{
             .name = "todo_name func",
             .src = self_ast_node_index,
             .params = param_type_refs.items,
             .ret = ret_type_ref,
         },
-    });
+    };
     return DocData.WalkResult{ .type = self.types.items.len - 1 };
 }
 
@@ -2176,7 +2134,7 @@ fn collectUnionFieldInfo(
     file: *File,
     scope: *Scope,
     fields_len: usize,
-    field_type_refs: *std.ArrayListUnmanaged(DocData.TypeRef),
+    field_type_refs: *std.ArrayListUnmanaged(DocData.WalkResult),
     field_name_indexes: *std.ArrayListUnmanaged(usize),
     ei: usize,
 ) !void {
@@ -2222,10 +2180,7 @@ fn collectUnionFieldInfo(
         // type
         {
             const walk_result = try self.walkRef(file, scope, field_type);
-            try field_type_refs.append(
-                self.arena,
-                walkResultToTypeRef(walk_result),
-            );
+            try field_type_refs.append(self.arena, walk_result);
         }
 
         // ast node
@@ -2248,7 +2203,7 @@ fn collectStructFieldInfo(
     file: *File,
     scope: *Scope,
     fields_len: usize,
-    field_type_refs: *std.ArrayListUnmanaged(DocData.TypeRef),
+    field_type_refs: *std.ArrayListUnmanaged(DocData.WalkResult),
     field_name_indexes: *std.ArrayListUnmanaged(usize),
     ei: usize,
 ) !void {
@@ -2291,10 +2246,7 @@ fn collectStructFieldInfo(
         // type
         {
             const walk_result = try self.walkRef(file, scope, field_type);
-            try field_type_refs.append(
-                self.arena,
-                walkResultToTypeRef(walk_result),
-            );
+            try field_type_refs.append(self.arena, walk_result);
         }
 
         // ast node
@@ -2334,17 +2286,24 @@ fn walkRef(
                 });
             },
             .undef => {
-                return DocData.WalkResult{ .@"undefined" = .unspecified };
+                var t = try self.arena.create(DocData.WalkResult);
+                t.* = .void;
+
+                return DocData.WalkResult{ .@"undefined" = t };
             },
             .zero => {
+                var t = try self.arena.create(DocData.WalkResult);
+                t.* = .{ .type = @enumToInt(Ref.comptime_int_type) };
                 return DocData.WalkResult{ .int = .{
-                    .typeRef = .{ .type = @enumToInt(Ref.comptime_int_type) },
+                    .typeRef = t,
                     .value = 0,
                 } };
             },
             .one => {
+                var t = try self.arena.create(DocData.WalkResult);
+                t.* = .{ .type = @enumToInt(Ref.comptime_int_type) };
                 return DocData.WalkResult{ .int = .{
-                    .typeRef = .{ .type = @enumToInt(Ref.comptime_int_type) },
+                    .typeRef = t,
                     .value = 1,
                 } };
             },
@@ -2356,7 +2315,9 @@ fn walkRef(
                 return DocData.WalkResult{ .@"unreachable" = {} };
             },
             .null_value => {
-                return DocData.WalkResult{ .@"null" = .unspecified };
+                var t = try self.arena.create(DocData.WalkResult);
+                t.* = .void;
+                return DocData.WalkResult{ .@"null" = t };
             },
             .bool_true => {
                 return DocData.WalkResult{ .bool = true };
@@ -2365,20 +2326,27 @@ fn walkRef(
                 return DocData.WalkResult{ .bool = false };
             },
             .empty_struct => {
+                var t = try self.arena.create(DocData.WalkResult);
+                t.* = .void;
+
                 return DocData.WalkResult{ .@"struct" = .{
-                    .typeRef = .unspecified,
+                    .typeRef = t,
                     .fieldVals = &.{},
                 } };
             },
             .zero_usize => {
+                var t = try self.arena.create(DocData.WalkResult);
+                t.* = .{ .type = @enumToInt(Ref.usize_type) };
                 return DocData.WalkResult{ .int = .{
-                    .typeRef = .{ .type = @enumToInt(Ref.usize_type) },
+                    .typeRef = t,
                     .value = 0,
                 } };
             },
             .one_usize => {
+                var t = try self.arena.create(DocData.WalkResult);
+                t.* = .{ .type = @enumToInt(Ref.usize_type) };
                 return DocData.WalkResult{ .int = .{
-                    .typeRef = .{ .type = @enumToInt(Ref.usize_type) },
+                    .typeRef = t,
                     .value = 1,
                 } };
             },
@@ -2408,37 +2376,19 @@ fn walkRef(
     }
 }
 
-/// Maps some `DocData.WalkResult` cases to `DocData.TypeRef`.
-/// Correct code should never cause this function to fail but
-/// incorrect code might (eg: `const foo: 5 = undefined;`)
-fn walkResultToTypeRef(wr: DocData.WalkResult) DocData.TypeRef {
-    return switch (wr) {
-        else => std.debug.panic(
-            "TODO: handle `{s}` in `walkResultToTypeRef`\n",
-            .{@tagName(wr)},
-        ),
-
-        .typeOf => |v| .{ .typeOf = v },
-        .comptimeExpr => |v| .{ .comptimeExpr = v },
-        .declPath => |v| .{ .declPath = v },
-        .type => |v| .{ .type = v },
-        .call => |v| .{ .call = v },
-    };
-}
-
 /// Given a WalkResult, tries to find its type.
 /// Used to analyze instructions like `array_init`, which require us to
 /// inspect its first element to find out the array type.
-fn typeOfWalkResult(wr: DocData.WalkResult) DocData.TypeRef {
+fn typeOfWalkResult(wr: DocData.WalkResult) DocData.WalkResult {
     return switch (wr) {
         else => std.debug.panic(
             "TODO: handle `{s}` in typeOfWalkResult\n",
             .{@tagName(wr)},
         ),
         .type => .{ .type = @enumToInt(DocData.DocTypeKinds.Type) },
-        .int => |v| v.typeRef,
-        .float => |v| v.typeRef,
-        .array => |v| v.typeRef,
+        .int => |v| v.typeRef.*,
+        .float => |v| v.typeRef.*,
+        .array => |v| v.typeRef.*,
     };
 }
 
@@ -2454,3 +2404,12 @@ fn panicWithContext(file: *File, inst: usize, comptime fmt: []const u8, args: an
     std.debug.print("Context [{s}] % {}\n", .{ file.sub_file_path, inst });
     std.debug.panic(fmt, args);
 }
+
+fn cteTodo(self: *Autodoc, msg: []const u8) error{OutOfMemory}!DocData.WalkResult {
+    const cte_slot_index = self.comptime_exprs.items.len;
+    try self.comptime_exprs.append(self.arena, .{
+        .code = msg,
+        .typeRef = .{ .type = @enumToInt(DocData.DocTypeKinds.ComptimeExpr) },
+    });
+    return DocData.WalkResult{ .comptimeExpr = cte_slot_index };
+}