Commit 426e737292

Loris Cro <kappaloris@gmail.com>
2023-07-21 18:45:15
autodoc: avoid recursing reference loops in call instructions
1 parent 61d5b7c
Changed files (1)
src/Autodoc.zig
@@ -328,7 +328,14 @@ 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, false);
+    _ = try self.walkInstruction(
+        file,
+        &root_scope,
+        .{},
+        Zir.main_struct_inst,
+        false,
+        null,
+    );
 
     if (self.ref_paths_pending_on_decls.count() > 0) {
         @panic("some decl paths were never fully analized (pending on decls)");
@@ -979,6 +986,15 @@ const AutodocErrors = error{
     UnexpectedEndOfFile,
 } || std.fs.File.OpenError || std.fs.File.ReadError;
 
+/// `call` instructions will have loopy references to themselves
+///  whenever an as_node is required for a complex expression.
+///  This type is used to keep track of dangerous instruction
+///  numbers that we definitely don't want to recurse into.
+const CallContext = struct {
+    inst: usize,
+    prev: ?*const CallContext,
+};
+
 /// Called when we need to analyze a Zir instruction.
 /// For example it gets called by `generateZirData` on instruction 0,
 /// which represents the top-level struct corresponding to the root file.
@@ -994,6 +1010,7 @@ fn walkInstruction(
     parent_src: SrcLocInfo,
     inst_index: usize,
     need_type: bool, // true if the caller needs us to provide also a typeRef
+    call_ctx: ?*const CallContext,
 ) AutodocErrors!DocData.WalkResult {
     const tags = file.zir.instructions.items(.tag);
     const data = file.zir.instructions.items(.data);
@@ -1082,6 +1099,7 @@ fn walkInstruction(
                     .{},
                     Zir.main_struct_inst,
                     false,
+                    call_ctx,
                 );
             }
 
@@ -1113,6 +1131,7 @@ fn walkInstruction(
                 .{},
                 Zir.main_struct_inst,
                 need_type,
+                call_ctx,
             );
         },
         .ret_type => {
@@ -1123,7 +1142,14 @@ fn walkInstruction(
         },
         .ret_node => {
             const un_node = data[inst_index].un_node;
-            return self.walkRef(file, parent_scope, parent_src, un_node.operand, false);
+            return self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                un_node.operand,
+                false,
+                call_ctx,
+            );
         },
         .ret_load => {
             const un_node = data[inst_index].un_node;
@@ -1154,7 +1180,14 @@ fn walkInstruction(
             }
 
             if (result_ref) |rr| {
-                return self.walkRef(file, parent_scope, parent_src, rr, need_type);
+                return self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    rr,
+                    need_type,
+                    call_ctx,
+                );
             }
 
             return DocData.WalkResult{
@@ -1163,11 +1196,25 @@ fn walkInstruction(
         },
         .closure_get => {
             const inst_node = data[inst_index].inst_node;
-            return try self.walkInstruction(file, parent_scope, parent_src, inst_node.inst, need_type);
+            return try self.walkInstruction(
+                file,
+                parent_scope,
+                parent_src,
+                inst_node.inst,
+                need_type,
+                call_ctx,
+            );
         },
         .closure_capture => {
             const un_tok = data[inst_index].un_tok;
-            return try self.walkRef(file, parent_scope, parent_src, un_tok.operand, need_type);
+            return try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                un_tok.operand,
+                need_type,
+                call_ctx,
+            );
         },
         .str => {
             const str = data[inst_index].str.get(file.zir);
@@ -1214,6 +1261,7 @@ fn walkInstruction(
                 parent_src,
                 un_node.operand,
                 false,
+                call_ctx,
             );
 
             const operand_index = self.exprs.items.len;
@@ -1284,6 +1332,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
             var start: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1291,6 +1340,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.start,
                 false,
+                call_ctx,
             );
 
             const lhs_index = self.exprs.items.len;
@@ -1317,6 +1367,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
             var start: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1324,6 +1375,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.start,
                 false,
+                call_ctx,
             );
             var end: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1331,6 +1383,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.end,
                 false,
+                call_ctx,
             );
 
             const lhs_index = self.exprs.items.len;
@@ -1359,6 +1412,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
             var start: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1366,6 +1420,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.start,
                 false,
+                call_ctx,
             );
             var end: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1373,6 +1428,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.end,
                 false,
+                call_ctx,
             );
             var sentinel: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1380,6 +1436,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.sentinel,
                 false,
+                call_ctx,
             );
 
             const lhs_index = self.exprs.items.len;
@@ -1410,6 +1467,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
             var start: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1417,6 +1475,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.start,
                 false,
+                call_ctx,
             );
             var len: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1424,6 +1483,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.len,
                 false,
+                call_ctx,
             );
             var sentinel_opt: ?DocData.WalkResult = if (extra.data.sentinel != .none)
                 try self.walkRef(
@@ -1432,6 +1492,7 @@ fn walkInstruction(
                     parent_src,
                     extra.data.sentinel,
                     false,
+                    call_ctx,
                 )
             else
                 null;
@@ -1492,6 +1553,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
             var rhs: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1499,6 +1561,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.rhs,
                 false,
+                call_ctx,
             );
 
             const lhs_index = self.exprs.items.len;
@@ -1536,6 +1599,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
             var rhs: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1543,6 +1607,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.rhs,
                 false,
+                call_ctx,
             );
 
             const lhs_index = self.exprs.items.len;
@@ -1599,7 +1664,14 @@ fn walkInstruction(
             const un_node = data[inst_index].un_node;
             const bin_index = self.exprs.items.len;
             try self.exprs.append(self.arena, .{ .builtin = .{ .param = 0 } });
-            const param = try self.walkRef(file, parent_scope, parent_src, un_node.operand, false);
+            const param = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                un_node.operand,
+                false,
+                call_ctx,
+            );
 
             const param_index = self.exprs.items.len;
             try self.exprs.append(self.arena, param.expr);
@@ -1623,6 +1695,7 @@ fn walkInstruction(
                 parent_src,
                 bool_br.lhs,
                 false,
+                call_ctx,
             );
             const lhs_index = self.exprs.items.len;
             try self.exprs.append(self.arena, lhs.expr);
@@ -1634,6 +1707,7 @@ fn walkInstruction(
                 parent_src,
                 file.zir.extra[extra.end..][extra.data.body_len - 1],
                 false,
+                call_ctx,
             );
             const rhs_index = self.exprs.items.len;
             try self.exprs.append(self.arena, rhs.expr);
@@ -1656,6 +1730,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.rhs,
                 false,
+                call_ctx,
             );
 
             const bin_index = self.exprs.items.len;
@@ -1670,6 +1745,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
 
             self.exprs.items[bin_index] = .{ .builtin = .{ .name = @tagName(tags[inst_index]), .param = rhs_index } };
@@ -1718,6 +1794,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
             var rhs: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1725,6 +1802,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.rhs,
                 false,
+                call_ctx,
             );
 
             const lhs_index = self.exprs.items.len;
@@ -1748,6 +1826,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
             var rhs: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1755,6 +1834,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.rhs,
                 false,
+                call_ctx,
             );
 
             const type_slot_index = self.types.items.len;
@@ -1778,6 +1858,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.lhs,
                 false,
+                call_ctx,
             );
             var rhs: DocData.WalkResult = try self.walkRef(
                 file,
@@ -1785,6 +1866,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.rhs,
                 false,
+                call_ctx,
             );
             const type_slot_index = self.types.items.len;
             try self.types.append(self.arena, .{ .ErrorUnion = .{
@@ -1820,6 +1902,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.elem_type,
                 false,
+                call_ctx,
             );
 
             // @check if `addrspace`, `bit_start` and `host_size` really need to be
@@ -1827,7 +1910,14 @@ fn walkInstruction(
             var sentinel: ?DocData.Expr = null;
             if (ptr.flags.has_sentinel) {
                 const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
-                const ref_result = try self.walkRef(file, parent_scope, parent_src, ref, false);
+                const ref_result = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    ref,
+                    false,
+                    call_ctx,
+                );
                 sentinel = ref_result.expr;
                 extra_index += 1;
             }
@@ -1835,21 +1925,42 @@ fn walkInstruction(
             var @"align": ?DocData.Expr = null;
             if (ptr.flags.has_align) {
                 const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
-                const ref_result = try self.walkRef(file, parent_scope, parent_src, ref, false);
+                const ref_result = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    ref,
+                    false,
+                    call_ctx,
+                );
                 @"align" = ref_result.expr;
                 extra_index += 1;
             }
             var address_space: ?DocData.Expr = null;
             if (ptr.flags.has_addrspace) {
                 const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
-                const ref_result = try self.walkRef(file, parent_scope, parent_src, ref, false);
+                const ref_result = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    ref,
+                    false,
+                    call_ctx,
+                );
                 address_space = ref_result.expr;
                 extra_index += 1;
             }
             var bit_start: ?DocData.Expr = null;
             if (ptr.flags.has_bit_range) {
                 const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
-                const ref_result = try self.walkRef(file, parent_scope, parent_src, ref, false);
+                const ref_result = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    ref,
+                    false,
+                    call_ctx,
+                );
                 address_space = ref_result.expr;
                 extra_index += 1;
             }
@@ -1857,7 +1968,14 @@ fn walkInstruction(
             var host_size: ?DocData.Expr = null;
             if (ptr.flags.has_bit_range) {
                 const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
-                const ref_result = try self.walkRef(file, parent_scope, parent_src, ref, false);
+                const ref_result = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    ref,
+                    false,
+                    call_ctx,
+                );
                 host_size = ref_result.expr;
             }
 
@@ -1888,8 +2006,22 @@ fn walkInstruction(
             const pl_node = data[inst_index].pl_node;
 
             const bin = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index).data;
-            const len = try self.walkRef(file, parent_scope, parent_src, bin.lhs, false);
-            const child = try self.walkRef(file, parent_scope, parent_src, bin.rhs, false);
+            const len = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                bin.lhs,
+                false,
+                call_ctx,
+            );
+            const child = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                bin.rhs,
+                false,
+                call_ctx,
+            );
 
             const type_slot_index = self.types.items.len;
             try self.types.append(self.arena, .{
@@ -1907,9 +2039,30 @@ fn walkInstruction(
         .array_type_sentinel => {
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.ArrayTypeSentinel, pl_node.payload_index);
-            const len = try self.walkRef(file, parent_scope, parent_src, extra.data.len, false);
-            const sentinel = try self.walkRef(file, parent_scope, parent_src, extra.data.sentinel, false);
-            const elem_type = try self.walkRef(file, parent_scope, parent_src, extra.data.elem_type, false);
+            const len = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                extra.data.len,
+                false,
+                call_ctx,
+            );
+            const sentinel = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                extra.data.sentinel,
+                false,
+                call_ctx,
+            );
+            const elem_type = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                extra.data.elem_type,
+                false,
+                call_ctx,
+            );
 
             const type_slot_index = self.types.items.len;
             try self.types.append(self.arena, .{
@@ -1931,10 +2084,24 @@ fn walkInstruction(
             const array_data = try self.arena.alloc(usize, operands.len - 1);
 
             std.debug.assert(operands.len > 0);
-            var array_type = try self.walkRef(file, parent_scope, parent_src, operands[0], false);
+            var array_type = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                operands[0],
+                false,
+                call_ctx,
+            );
 
             for (operands[1..], 0..) |op, idx| {
-                const wr = try self.walkRef(file, parent_scope, parent_src, op, false);
+                const wr = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    op,
+                    false,
+                    call_ctx,
+                );
                 const expr_index = self.exprs.items.len;
                 try self.exprs.append(self.arena, wr.expr);
                 array_data[idx] = expr_index;
@@ -1952,7 +2119,14 @@ fn walkInstruction(
             const array_data = try self.arena.alloc(usize, operands.len);
 
             for (operands, 0..) |op, idx| {
-                const wr = try self.walkRef(file, parent_scope, parent_src, op, false);
+                const wr = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    op,
+                    false,
+                    call_ctx,
+                );
                 const expr_index = self.exprs.items.len;
                 try self.exprs.append(self.arena, wr.expr);
                 array_data[idx] = expr_index;
@@ -1970,10 +2144,24 @@ fn walkInstruction(
             const array_data = try self.arena.alloc(usize, operands.len - 1);
 
             std.debug.assert(operands.len > 0);
-            var array_type = try self.walkRef(file, parent_scope, parent_src, operands[0], false);
+            var array_type = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                operands[0],
+                false,
+                call_ctx,
+            );
 
             for (operands[1..], 0..) |op, idx| {
-                const wr = try self.walkRef(file, parent_scope, parent_src, op, false);
+                const wr = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    op,
+                    false,
+                    call_ctx,
+                );
                 const expr_index = self.exprs.items.len;
                 try self.exprs.append(self.arena, wr.expr);
                 array_data[idx] = expr_index;
@@ -2002,7 +2190,14 @@ fn walkInstruction(
             const array_data = try self.arena.alloc(usize, operands.len);
 
             for (operands, 0..) |op, idx| {
-                const wr = try self.walkRef(file, parent_scope, parent_src, op, false);
+                const wr = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    op,
+                    false,
+                    call_ctx,
+                );
                 const expr_index = self.exprs.items.len;
                 try self.exprs.append(self.arena, wr.expr);
                 array_data[idx] = expr_index;
@@ -2041,6 +2236,7 @@ fn walkInstruction(
                 parent_src,
                 un_node.operand,
                 need_type,
+                call_ctx,
             );
             switch (operand.expr) {
                 .int => |*int| int.negated = true,
@@ -2065,6 +2261,7 @@ fn walkInstruction(
                 parent_src,
                 un_node.operand,
                 false,
+                call_ctx,
             );
             const operand_index = self.exprs.items.len;
             try self.exprs.append(self.arena, operand.expr);
@@ -2083,6 +2280,7 @@ fn walkInstruction(
                 parent_src,
                 un_node.operand,
                 need_type,
+                call_ctx,
             );
             const operand_index = self.exprs.items.len;
             try self.exprs.append(self.arena, operand.expr);
@@ -2101,6 +2299,7 @@ fn walkInstruction(
                 parent_src,
                 un_node.operand,
                 false,
+                call_ctx,
             );
             const operand_index = self.exprs.items.len;
             try self.exprs.append(self.arena, operand.expr);
@@ -2115,7 +2314,14 @@ fn walkInstruction(
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.SwitchBlock, pl_node.payload_index);
 
-            const switch_cond = try self.walkRef(file, parent_scope, parent_src, extra.data.operand, false);
+            const switch_cond = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                extra.data.operand,
+                false,
+                call_ctx,
+            );
             const cond_index = self.exprs.items.len;
             try self.exprs.append(self.arena, switch_cond.expr);
             _ = cond_index;
@@ -2162,6 +2368,7 @@ fn walkInstruction(
                 parent_src,
                 un_node.operand,
                 need_type,
+                call_ctx,
             );
             const operand_index = self.exprs.items.len;
             try self.exprs.append(self.arena, operand.expr);
@@ -2181,6 +2388,7 @@ fn walkInstruction(
                 parent_src,
                 data[body].@"break".operand,
                 false,
+                call_ctx,
             );
 
             const operand_index = self.exprs.items.len;
@@ -2201,6 +2409,7 @@ fn walkInstruction(
                 parent_src,
                 un_node.operand,
                 need_type,
+                call_ctx,
             );
 
             const operand_index = self.exprs.items.len;
@@ -2214,12 +2423,31 @@ fn walkInstruction(
         .as_node, .as_shift_operand => {
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.As, pl_node.payload_index);
+
+            // Skip the as_node if the destination type is a call instruction
+            if (Zir.refToIndex(extra.data.dest_type)) |dti| {
+                var maybe_cc = call_ctx;
+                while (maybe_cc) |cc| : (maybe_cc = cc.prev) {
+                    if (cc.inst == dti) {
+                        return try self.walkRef(
+                            file,
+                            parent_scope,
+                            parent_src,
+                            extra.data.operand,
+                            false,
+                            call_ctx,
+                        );
+                    }
+                }
+            }
+
             const dest_type_walk = try self.walkRef(
                 file,
                 parent_scope,
                 parent_src,
                 extra.data.dest_type,
                 false,
+                call_ctx,
             );
 
             const operand = try self.walkRef(
@@ -2228,6 +2456,7 @@ fn walkInstruction(
                 parent_src,
                 extra.data.operand,
                 false,
+                call_ctx,
             );
 
             const operand_idx = self.exprs.items.len;
@@ -2257,6 +2486,7 @@ fn walkInstruction(
                 parent_src,
                 un_node.operand,
                 false,
+                call_ctx,
             );
 
             const operand_idx = self.types.items.len;
@@ -2332,7 +2562,14 @@ fn walkInstruction(
                     }
                 }
 
-                break :blk try self.walkRef(file, parent_scope, parent_src, lhs_ref, false);
+                break :blk try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    lhs_ref,
+                    false,
+                    call_ctx,
+                );
             };
             try path.append(self.arena, wr.expr);
 
@@ -2400,6 +2637,7 @@ fn walkInstruction(
                     return res;
                 },
                 need_type,
+                call_ctx,
             );
         },
         .break_inline => {
@@ -2410,6 +2648,7 @@ fn walkInstruction(
                 parent_src,
                 @"break".operand,
                 need_type,
+                call_ctx,
             );
         },
         .struct_init => {
@@ -2448,6 +2687,7 @@ fn walkInstruction(
                             field_src,
                             field_extra.data.container_type,
                             false,
+                            call_ctx,
                         );
                         type_ref = wr.expr;
                     }
@@ -2459,6 +2699,7 @@ fn walkInstruction(
                     parent_src,
                     init_extra.data.init,
                     need_type,
+                    call_ctx,
                 );
                 fv.* = .{ .name = field_name, .val = value };
             }
@@ -2477,6 +2718,7 @@ fn walkInstruction(
                 parent_src,
                 un_node.operand,
                 false,
+                call_ctx,
             );
 
             return DocData.WalkResult{
@@ -2503,6 +2745,7 @@ fn walkInstruction(
                     parent_src,
                     init_extra.data.init,
                     need_type,
+                    call_ctx,
                 );
                 fv.* = .{ .name = field_name, .val = value };
                 idx = init_extra.end;
@@ -2576,7 +2819,14 @@ fn walkInstruction(
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.Call, pl_node.payload_index);
 
-            const callee = try self.walkRef(file, parent_scope, parent_src, extra.data.callee, need_type);
+            const callee = try self.walkRef(
+                file,
+                parent_scope,
+                parent_src,
+                extra.data.callee,
+                need_type,
+                call_ctx,
+            );
 
             const args_len = extra.data.flags.args_len;
             var args = try self.arena.alloc(DocData.Expr, args_len);
@@ -2591,7 +2841,17 @@ fn walkInstruction(
                 //       to show discrepancies between the types of provided
                 //       arguments and the types declared in the function
                 //       signature for its parameters.
-                const wr = try self.walkRef(file, parent_scope, parent_src, ref, false);
+                const wr = try self.walkRef(
+                    file,
+                    parent_scope,
+                    parent_src,
+                    ref,
+                    false,
+                    &.{
+                        .inst = inst_index,
+                        .prev = call_ctx,
+                    },
+                );
                 args[i] = wr.expr;
             }
 
@@ -2639,6 +2899,7 @@ fn walkInstruction(
                 self_ast_node_index,
                 type_slot_index,
                 tags[inst_index] == .func_inferred,
+                call_ctx,
             );
 
             return result;
@@ -2654,6 +2915,7 @@ fn walkInstruction(
                 inst_index,
                 self_ast_node_index,
                 type_slot_index,
+                call_ctx,
             );
 
             return result;
@@ -2678,7 +2940,14 @@ fn walkInstruction(
 
                     var array_type: ?DocData.Expr = null;
                     for (args, 0..) |arg, idx| {
-                        const wr = try self.walkRef(file, parent_scope, parent_src, arg, idx == 0);
+                        const wr = try self.walkRef(
+                            file,
+                            parent_scope,
+                            parent_src,
+                            arg,
+                            idx == 0,
+                            call_ctx,
+                        );
                         if (idx == 0) {
                             array_type = wr.typeRef;
                         }
@@ -2740,6 +3009,7 @@ fn walkInstruction(
                         src_info,
                         &decl_indexes,
                         &priv_decl_indexes,
+                        call_ctx,
                     );
 
                     self.types.items[type_slot_index] = .{
@@ -2778,7 +3048,14 @@ fn walkInstruction(
                     if (small.has_lib_name) extra_index += 1;
                     if (small.has_align) extra_index += 1;
 
-                    const var_type = try self.walkRef(file, parent_scope, parent_src, extra.data.var_type, need_type);
+                    const var_type = try self.walkRef(
+                        file,
+                        parent_scope,
+                        parent_src,
+                        extra.data.var_type,
+                        need_type,
+                        call_ctx,
+                    );
 
                     var value: DocData.WalkResult = .{
                         .typeRef = var_type.expr,
@@ -2787,7 +3064,14 @@ fn walkInstruction(
 
                     if (small.has_init) {
                         const var_init_ref = @as(Ref, @enumFromInt(file.zir.extra[extra_index]));
-                        const var_init = try self.walkRef(file, parent_scope, parent_src, var_init_ref, need_type);
+                        const var_init = try self.walkRef(
+                            file,
+                            parent_scope,
+                            parent_src,
+                            var_init_ref,
+                            need_type,
+                            call_ctx,
+                        );
                         value.expr = var_init.expr;
                         value.typeRef = var_init.typeRef;
                     }
@@ -2853,6 +3137,7 @@ fn walkInstruction(
                         src_info,
                         &decl_indexes,
                         &priv_decl_indexes,
+                        call_ctx,
                     );
 
                     // Analyze the tag once all decls have been analyzed
@@ -2862,6 +3147,7 @@ fn walkInstruction(
                         parent_src,
                         tt_ref,
                         false,
+                        call_ctx,
                     )).expr else null;
 
                     // Fields
@@ -2883,6 +3169,7 @@ fn walkInstruction(
                         &field_type_refs,
                         &field_name_indexes,
                         extra_index,
+                        call_ctx,
                     );
 
                     self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
@@ -2948,7 +3235,14 @@ fn walkInstruction(
                         const tag_type = file.zir.extra[extra_index];
                         extra_index += 1;
                         const tag_ref = @as(Ref, @enumFromInt(tag_type));
-                        const wr = try self.walkRef(file, parent_scope, parent_src, tag_ref, false);
+                        const wr = try self.walkRef(
+                            file,
+                            parent_scope,
+                            parent_src,
+                            tag_ref,
+                            false,
+                            call_ctx,
+                        );
                         break :blk wr.expr;
                     } else null;
 
@@ -2974,6 +3268,7 @@ fn walkInstruction(
                         src_info,
                         &decl_indexes,
                         &priv_decl_indexes,
+                        call_ctx,
                     );
 
                     // const body = file.zir.extra[extra_index..][0..body_len];
@@ -3005,7 +3300,14 @@ fn walkInstruction(
                             const value_expr: ?DocData.Expr = if (has_value) blk: {
                                 const value_ref = file.zir.extra[extra_index];
                                 extra_index += 1;
-                                const value = try self.walkRef(file, &scope, src_info, @as(Ref, @enumFromInt(value_ref)), false);
+                                const value = try self.walkRef(
+                                    file,
+                                    &scope,
+                                    src_info,
+                                    @as(Ref, @enumFromInt(value_ref)),
+                                    false,
+                                    call_ctx,
+                                );
                                 break :blk value.expr;
                             } else null;
                             try field_values.append(self.arena, value_expr);
@@ -3095,14 +3397,28 @@ fn walkInstruction(
                         extra_index += 1; // backing_int_body_len
                         if (backing_int_body_len == 0) {
                             const backing_int_ref = @as(Ref, @enumFromInt(file.zir.extra[extra_index]));
-                            const backing_int_res = try self.walkRef(file, &scope, src_info, backing_int_ref, true);
+                            const backing_int_res = try self.walkRef(
+                                file,
+                                &scope,
+                                src_info,
+                                backing_int_ref,
+                                true,
+                                call_ctx,
+                            );
                             backing_int = backing_int_res.expr;
                             extra_index += 1; // backing_int_ref
                         } else {
                             const backing_int_body = file.zir.extra[extra_index..][0..backing_int_body_len];
                             const break_inst = backing_int_body[backing_int_body.len - 1];
                             const operand = data[break_inst].@"break".operand;
-                            const backing_int_res = try self.walkRef(file, &scope, src_info, operand, true);
+                            const backing_int_res = try self.walkRef(
+                                file,
+                                &scope,
+                                src_info,
+                                operand,
+                                true,
+                                call_ctx,
+                            );
                             backing_int = backing_int_res.expr;
                             extra_index += backing_int_body_len; // backing_int_body_inst
                         }
@@ -3123,6 +3439,7 @@ fn walkInstruction(
                         src_info,
                         &decl_indexes,
                         &priv_decl_indexes,
+                        call_ctx,
                     );
 
                     var field_type_refs: std.ArrayListUnmanaged(DocData.Expr) = .{};
@@ -3138,6 +3455,7 @@ fn walkInstruction(
                         &field_name_indexes,
                         extra_index,
                         small.is_tuple,
+                        call_ctx,
                     );
 
                     self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
@@ -3194,7 +3512,14 @@ fn walkInstruction(
                     const extra = file.zir.extraData(Zir.Inst.UnNode, extended.operand).data;
                     const bin_index = self.exprs.items.len;
                     try self.exprs.append(self.arena, .{ .builtin = .{ .param = 0 } });
-                    const param = try self.walkRef(file, parent_scope, parent_src, extra.operand, false);
+                    const param = try self.walkRef(
+                        file,
+                        parent_scope,
+                        parent_src,
+                        extra.operand,
+                        false,
+                        call_ctx,
+                    );
 
                     const param_index = self.exprs.items.len;
                     try self.exprs.append(self.arena, param.expr);
@@ -3213,7 +3538,14 @@ fn walkInstruction(
                     const extra = file.zir.extraData(Zir.Inst.UnNode, extended.operand).data;
                     const bin_index = self.exprs.items.len;
                     try self.exprs.append(self.arena, .{ .builtin = .{ .param = 0 } });
-                    const param = try self.walkRef(file, parent_scope, parent_src, extra.operand, false);
+                    const param = try self.walkRef(
+                        file,
+                        parent_scope,
+                        parent_src,
+                        extra.operand,
+                        false,
+                        call_ctx,
+                    );
 
                     const param_index = self.exprs.items.len;
                     try self.exprs.append(self.arena, param.expr);
@@ -3241,6 +3573,7 @@ fn walkInstruction(
                         parent_src,
                         extra.ptr,
                         false,
+                        call_ctx,
                     );
                     try self.exprs.append(self.arena, ptr.expr);
 
@@ -3251,6 +3584,7 @@ fn walkInstruction(
                         parent_src,
                         extra.expected_value,
                         false,
+                        call_ctx,
                     );
                     try self.exprs.append(self.arena, expected_value.expr);
 
@@ -3261,6 +3595,7 @@ fn walkInstruction(
                         parent_src,
                         extra.new_value,
                         false,
+                        call_ctx,
                     );
                     try self.exprs.append(self.arena, new_value.expr);
 
@@ -3271,6 +3606,7 @@ fn walkInstruction(
                         parent_src,
                         extra.success_order,
                         false,
+                        call_ctx,
                     );
                     try self.exprs.append(self.arena, success_order.expr);
 
@@ -3281,6 +3617,7 @@ fn walkInstruction(
                         parent_src,
                         extra.failure_order,
                         false,
+                        call_ctx,
                     );
                     try self.exprs.append(self.arena, failure_order.expr);
 
@@ -3319,6 +3656,7 @@ fn analyzeAllDecls(
     parent_src: SrcLocInfo,
     decl_indexes: *std.ArrayListUnmanaged(usize),
     priv_decl_indexes: *std.ArrayListUnmanaged(usize),
+    call_ctx: ?*const CallContext,
 ) AutodocErrors!usize {
     const first_decl_indexes_slot = decl_indexes.items.len;
     const original_it = file.zir.declIterator(@as(u32, @intCast(parent_inst_index)));
@@ -3358,6 +3696,7 @@ fn analyzeAllDecls(
                                 decl_indexes,
                                 priv_decl_indexes,
                                 d,
+                                call_ctx,
                             );
                         },
                     }
@@ -3387,6 +3726,7 @@ fn analyzeAllDecls(
             decl_indexes,
             priv_decl_indexes,
             d,
+            call_ctx,
         );
     }
 
@@ -3420,6 +3760,7 @@ fn analyzeDecl(
     decl_indexes: *std.ArrayListUnmanaged(usize),
     priv_decl_indexes: *std.ArrayListUnmanaged(usize),
     d: Zir.DeclIterator.Item,
+    call_ctx: ?*const CallContext,
 ) AutodocErrors!void {
     const data = file.zir.instructions.items(.data);
     const is_pub = @as(u1, @truncate(d.flags >> 0)) != 0;
@@ -3497,7 +3838,14 @@ fn analyzeDecl(
         break :idx idx;
     };
 
-    const walk_result = try self.walkInstruction(file, scope, decl_src, value_index, true);
+    const walk_result = try self.walkInstruction(
+        file,
+        scope,
+        decl_src,
+        value_index,
+        true,
+        call_ctx,
+    );
 
     const kind: []const u8 = if (try self.declIsVar(file, value_pl_node.src_node, parent_src)) "var" else "const";
 
@@ -3545,6 +3893,7 @@ fn analyzeUsingnamespaceDecl(
     decl_indexes: *std.ArrayListUnmanaged(usize),
     priv_decl_indexes: *std.ArrayListUnmanaged(usize),
     d: Zir.DeclIterator.Item,
+    call_ctx: ?*const CallContext,
 ) AutodocErrors!void {
     const data = file.zir.instructions.items(.data);
 
@@ -3574,7 +3923,14 @@ fn analyzeUsingnamespaceDecl(
         break :idx idx;
     };
 
-    const walk_result = try self.walkInstruction(file, scope, decl_src, value_index, true);
+    const walk_result = try self.walkInstruction(
+        file,
+        scope,
+        decl_src,
+        value_index,
+        true,
+        call_ctx,
+    );
 
     const decl_slot_index = self.decls.items.len;
     try self.decls.append(self.arena, .{
@@ -4192,6 +4548,7 @@ fn analyzeFancyFunction(
     inst_index: usize,
     self_ast_node_index: usize,
     type_slot_index: usize,
+    call_ctx: ?*const CallContext,
 ) AutodocErrors!DocData.WalkResult {
     const tags = file.zir.instructions.items(.tag);
     const data = file.zir.instructions.items(.data);
@@ -4253,7 +4610,14 @@ fn analyzeFancyFunction(
 
                 const break_index = file.zir.extra[extra.end..][extra.data.body_len - 1];
                 const break_operand = data[break_index].@"break".operand;
-                const param_type_ref = try self.walkRef(file, scope, parent_src, break_operand, false);
+                const param_type_ref = try self.walkRef(
+                    file,
+                    scope,
+                    parent_src,
+                    break_operand,
+                    false,
+                    call_ctx,
+                );
 
                 param_type_refs.appendAssumeCapacity(param_type_ref.expr);
             },
@@ -4277,7 +4641,14 @@ fn analyzeFancyFunction(
     if (extra.data.bits.has_align_ref) {
         const align_ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
         align_index = self.exprs.items.len;
-        _ = try self.walkRef(file, scope, parent_src, align_ref, false);
+        _ = try self.walkRef(
+            file,
+            scope,
+            parent_src,
+            align_ref,
+            false,
+            call_ctx,
+        );
         extra_index += 1;
     } else if (extra.data.bits.has_align_body) {
         const align_body_len = file.zir.extra[extra_index];
@@ -4294,7 +4665,14 @@ fn analyzeFancyFunction(
     if (extra.data.bits.has_addrspace_ref) {
         const addrspace_ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
         addrspace_index = self.exprs.items.len;
-        _ = try self.walkRef(file, scope, parent_src, addrspace_ref, false);
+        _ = try self.walkRef(
+            file,
+            scope,
+            parent_src,
+            addrspace_ref,
+            false,
+            call_ctx,
+        );
         extra_index += 1;
     } else if (extra.data.bits.has_addrspace_body) {
         const addrspace_body_len = file.zir.extra[extra_index];
@@ -4311,7 +4689,14 @@ fn analyzeFancyFunction(
     if (extra.data.bits.has_section_ref) {
         const section_ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
         section_index = self.exprs.items.len;
-        _ = try self.walkRef(file, scope, parent_src, section_ref, false);
+        _ = try self.walkRef(
+            file,
+            scope,
+            parent_src,
+            section_ref,
+            false,
+            call_ctx,
+        );
         extra_index += 1;
     } else if (extra.data.bits.has_section_body) {
         const section_body_len = file.zir.extra[extra_index];
@@ -4327,7 +4712,14 @@ fn analyzeFancyFunction(
     var cc_index: ?usize = null;
     if (extra.data.bits.has_cc_ref and !extra.data.bits.has_cc_body) {
         const cc_ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
-        const cc_expr = try self.walkRef(file, scope, parent_src, cc_ref, false);
+        const cc_expr = try self.walkRef(
+            file,
+            scope,
+            parent_src,
+            cc_ref,
+            false,
+            call_ctx,
+        );
 
         cc_index = self.exprs.items.len;
         try self.exprs.append(self.arena, cc_expr.expr);
@@ -4341,7 +4733,14 @@ fn analyzeFancyFunction(
         // We assume the body ends with a break_inline
         const break_index = cc_body[cc_body.len - 1];
         const break_operand = data[break_index].@"break".operand;
-        const cc_expr = try self.walkRef(file, scope, parent_src, break_operand, false);
+        const cc_expr = try self.walkRef(
+            file,
+            scope,
+            parent_src,
+            break_operand,
+            false,
+            call_ctx,
+        );
 
         cc_index = self.exprs.items.len;
         try self.exprs.append(self.arena, cc_expr.expr);
@@ -4357,14 +4756,28 @@ fn analyzeFancyFunction(
             .none => DocData.Expr{ .void = .{} },
             else => blk: {
                 const ref = fn_info.ret_ty_ref;
-                const wr = try self.walkRef(file, scope, parent_src, ref, false);
+                const wr = try self.walkRef(
+                    file,
+                    scope,
+                    parent_src,
+                    ref,
+                    false,
+                    call_ctx,
+                );
                 break :blk wr.expr;
             },
         },
         else => blk: {
             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, parent_src, break_operand, false);
+            const wr = try self.walkRef(
+                file,
+                scope,
+                parent_src,
+                break_operand,
+                false,
+                call_ctx,
+            );
             break :blk wr.expr;
         },
     };
@@ -4381,6 +4794,7 @@ fn analyzeFancyFunction(
                     scope,
                     parent_src,
                     fn_info.body[0],
+                    call_ctx,
                 );
             } else {
                 break :blk null;
@@ -4426,6 +4840,7 @@ fn analyzeFunction(
     self_ast_node_index: usize,
     type_slot_index: usize,
     ret_is_inferred_error_set: bool,
+    call_ctx: ?*const CallContext,
 ) AutodocErrors!DocData.WalkResult {
     const tags = file.zir.instructions.items(.tag);
     const data = file.zir.instructions.items(.data);
@@ -4487,7 +4902,14 @@ fn analyzeFunction(
 
                 const break_index = file.zir.extra[extra.end..][extra.data.body_len - 1];
                 const break_operand = data[break_index].@"break".operand;
-                const param_type_ref = try self.walkRef(file, scope, parent_src, break_operand, false);
+                const param_type_ref = try self.walkRef(
+                    file,
+                    scope,
+                    parent_src,
+                    break_operand,
+                    false,
+                    call_ctx,
+                );
 
                 param_type_refs.appendAssumeCapacity(param_type_ref.expr);
             },
@@ -4500,14 +4922,28 @@ fn analyzeFunction(
             .none => DocData.Expr{ .void = .{} },
             else => blk: {
                 const ref = fn_info.ret_ty_ref;
-                const wr = try self.walkRef(file, scope, parent_src, ref, false);
+                const wr = try self.walkRef(
+                    file,
+                    scope,
+                    parent_src,
+                    ref,
+                    false,
+                    call_ctx,
+                );
                 break :blk wr.expr;
             },
         },
         else => blk: {
             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, parent_src, break_operand, false);
+            const wr = try self.walkRef(
+                file,
+                scope,
+                parent_src,
+                break_operand,
+                false,
+                call_ctx,
+            );
             break :blk wr.expr;
         },
     };
@@ -4524,6 +4960,7 @@ fn analyzeFunction(
                     scope,
                     parent_src,
                     fn_info.body[0],
+                    call_ctx,
                 );
             } else {
                 break :blk null;
@@ -4570,6 +5007,7 @@ fn getGenericReturnType(
     scope: *Scope,
     parent_src: SrcLocInfo, // function decl line
     body_main_block: usize,
+    call_ctx: ?*const CallContext,
 ) !DocData.Expr {
     const tags = file.zir.instructions.items(.tag);
     const data = file.zir.instructions.items(.data);
@@ -4581,7 +5019,14 @@ fn getGenericReturnType(
     const maybe_ret_node = file.zir.extra[extra.end..][extra.data.body_len - 4];
     switch (tags[maybe_ret_node]) {
         .ret_node, .ret_load => {
-            const wr = try self.walkInstruction(file, scope, parent_src, maybe_ret_node, false);
+            const wr = try self.walkInstruction(
+                file,
+                scope,
+                parent_src,
+                maybe_ret_node,
+                false,
+                call_ctx,
+            );
             return wr.expr;
         },
         else => {
@@ -4599,6 +5044,7 @@ fn collectUnionFieldInfo(
     field_type_refs: *std.ArrayListUnmanaged(DocData.Expr),
     field_name_indexes: *std.ArrayListUnmanaged(usize),
     ei: usize,
+    call_ctx: ?*const CallContext,
 ) !void {
     if (fields_len == 0) return;
     var extra_index = ei;
@@ -4641,7 +5087,14 @@ fn collectUnionFieldInfo(
 
         // type
         {
-            const walk_result = try self.walkRef(file, scope, parent_src, field_type, false);
+            const walk_result = try self.walkRef(
+                file,
+                scope,
+                parent_src,
+                field_type,
+                false,
+                call_ctx,
+            );
             try field_type_refs.append(self.arena, walk_result.expr);
         }
 
@@ -4671,6 +5124,7 @@ fn collectStructFieldInfo(
     field_name_indexes: *std.ArrayListUnmanaged(usize),
     ei: usize,
     is_tuple: bool,
+    call_ctx: ?*const CallContext,
 ) !void {
     if (fields_len == 0) return;
     var extra_index = ei;
@@ -4744,7 +5198,14 @@ fn collectStructFieldInfo(
     for (fields) |field| {
         const type_expr = expr: {
             if (field.type_ref != .none) {
-                const walk_result = try self.walkRef(file, scope, parent_src, field.type_ref, false);
+                const walk_result = try self.walkRef(
+                    file,
+                    scope,
+                    parent_src,
+                    field.type_ref,
+                    false,
+                    call_ctx,
+                );
                 break :expr walk_result.expr;
             }
 
@@ -4760,7 +5221,14 @@ fn collectStructFieldInfo(
                 .col = 0,
                 .fields = null, // walkInstruction will fill `fields` if necessary
             });
-            const walk_result = try self.walkRef(file, scope, parent_src, operand, false);
+            const walk_result = try self.walkRef(
+                file,
+                scope,
+                parent_src,
+                operand,
+                false,
+                call_ctx,
+            );
             break :expr walk_result.expr;
         };
 
@@ -4776,7 +5244,14 @@ fn collectStructFieldInfo(
 
             const break_inst = body[body.len - 1];
             const operand = data[break_inst].@"break".operand;
-            const walk_result = try self.walkRef(file, scope, parent_src, operand, false);
+            const walk_result = try self.walkRef(
+                file,
+                scope,
+                parent_src,
+                operand,
+                false,
+                call_ctx,
+            );
             break :def walk_result.expr;
         };
 
@@ -4812,6 +5287,7 @@ fn walkRef(
     parent_src: SrcLocInfo,
     ref: Ref,
     need_type: bool, // true when the caller needs also a typeRef for the return value
+    call_ctx: ?*const CallContext,
 ) AutodocErrors!DocData.WalkResult {
     if (ref == .none) {
         return .{ .expr = .{ .comptimeExpr = 0 } };
@@ -4824,7 +5300,14 @@ fn walkRef(
             .expr = .{ .type = @intFromEnum(ref) },
         };
     } else if (Zir.refToIndex(ref)) |zir_index| {
-        return self.walkInstruction(file, parent_scope, parent_src, zir_index, need_type);
+        return self.walkInstruction(
+            file,
+            parent_scope,
+            parent_src,
+            zir_index,
+            need_type,
+            call_ctx,
+        );
     } else {
         switch (ref) {
             else => {