Commit 67f1d2b967

Loris Cro <kappaloris@gmail.com>
2022-03-13 06:12:17
autodoc: add basic support for more builtin
1 parent ec7f4d1
Changed files (1)
src/Autodoc.zig
@@ -532,13 +532,15 @@ const DocData = struct {
         // 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,
-            _: std.json.StringifyOptions,
+            options: std.json.StringifyOptions,
             w: anytype,
         ) !void {
             switch (self) {
+                .typeOf => |v| try std.json.stringify(v, options, w),
                 .unspecified, .@"anytype" => {
                     try w.print(
                         \\{{ "{s}":{{}} }}
@@ -591,13 +593,19 @@ const DocData = struct {
         array: Array,
         call: usize, // index in `calls`
         enumLiteral: []const u8,
+        typeOf: *WalkResult,
+        sizeOf: *WalkResult,
+        compileError: []const u8,
+        string: []const u8,
 
         const Struct = struct {
             typeRef: TypeRef,
-            fieldVals: []struct {
+            fieldVals: []FieldVal,
+
+            const FieldVal = struct {
                 name: []const u8,
                 val: WalkResult,
-            },
+            };
         };
         const Array = struct {
             typeRef: TypeRef,
@@ -647,6 +655,9 @@ const DocData = struct {
                 },
                 .@"undefined" => |v| try std.json.stringify(v, options, w),
                 .@"null" => |v| try std.json.stringify(v, options, w),
+                .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),
                 .@"struct" => |v| try std.json.stringify(
                     struct { @"struct": Struct }{ .@"struct" = v },
                     options,
@@ -709,11 +720,21 @@ fn walkInstruction(
 
     switch (tags[inst_index]) {
         else => {
-            std.debug.panic(
+            panicWithContext(
+                file,
+                inst_index,
                 "TODO: implement `{s}` for walkInstruction\n\n",
                 .{@tagName(tags[inst_index])},
             );
         },
+        .closure_get => {
+            const inst_node = data[inst_index].inst_node;
+            return try self.walkInstruction(file, parent_scope, inst_node.inst);
+        },
+        .closure_capture => {
+            const un_tok = data[inst_index].un_tok;
+            return try self.walkRef(file, parent_scope, un_tok.operand);
+        },
         .import => {
             const str_tok = data[inst_index].str_tok;
             const path = str_tok.get(file.zir);
@@ -749,11 +770,46 @@ fn walkInstruction(
 
             return new_file_walk_result;
         },
+        .str => {
+            const str = data[inst_index].str;
+            return DocData.WalkResult{
+                .string = str.get(file.zir),
+            };
+        },
+        .compile_error => {
+            const un_node = data[inst_index].un_node;
+            var operand: DocData.WalkResult = try self.walkRef(
+                file,
+                parent_scope,
+                un_node.operand,
+            );
+
+            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 };
+        },
         .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 };
+        },
         .int => {
             const int = data[inst_index].int;
             return DocData.WalkResult{
@@ -785,6 +841,25 @@ fn walkInstruction(
 
             return DocData.WalkResult{ .type = type_slot_index };
         },
+        .ptr_type => {
+            const ptr = data[inst_index].ptr_type;
+            const extra = file.zir.extraData(Zir.Inst.PtrType, ptr.payload_index);
+
+            const type_slot_index = self.types.items.len;
+            const elem_type_ref = try self.walkRef(
+                file,
+                parent_scope,
+                extra.data.elem_type,
+            );
+            try self.types.append(self.arena, .{
+                .Pointer = .{
+                    .size = ptr.size,
+                    .child = walkResultToTypeRef(elem_type_ref),
+                },
+            });
+
+            return DocData.WalkResult{ .type = type_slot_index };
+        },
         .array_type => {
             const bin = data[inst_index].bin;
             const len = try self.walkRef(file, parent_scope, bin.lhs);
@@ -848,6 +923,27 @@ fn walkInstruction(
             operand.int.negated = true; // only support ints for now
             return operand;
         },
+        .size_of => {
+            const un_node = data[inst_index].un_node;
+            var operand = try self.arena.create(DocData.WalkResult);
+            operand.* = try self.walkRef(
+                file,
+                parent_scope,
+                un_node.operand,
+            );
+            return DocData.WalkResult{ .sizeOf = operand };
+        },
+
+        .typeof => {
+            const un_node = data[inst_index].un_node;
+            var operand = try self.arena.create(DocData.WalkResult);
+            operand.* = try self.walkRef(
+                file,
+                parent_scope,
+                un_node.operand,
+            );
+            return DocData.WalkResult{ .typeOf = operand };
+        },
         .as_node => {
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.As, pl_node.payload_index);
@@ -857,11 +953,13 @@ fn walkInstruction(
             var operand = try self.walkRef(file, parent_scope, extra.data.operand);
 
             switch (operand) {
-                else => std.debug.panic(
+                else => panicWithContext(
+                    file,
+                    inst_index,
                     "TODO: handle {s} in `walkInstruction.as_node`\n",
                     .{@tagName(operand)},
                 ),
-                .declPath, .type => {},
+                .declPath, .type, .string => {},
                 // we don't do anything because up until now,
                 // I've only seen this used as such:
                 //       @as(@as(type, Baz), .{})
@@ -894,14 +992,16 @@ fn walkInstruction(
             });
             return res;
         },
-        .decl_val => {
+        .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 } };
         },
-        .field_val, .field_call_bind, .field_ptr => {
+        .field_val, .field_call_bind, .field_ptr, .field_type => {
+            // TODO: field type uses Zir.Inst.FieldType, it just happens to have the
+            // same layout as Zir.Inst.Field :^)
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.Field, pl_node.payload_index);
 
@@ -909,8 +1009,8 @@ fn walkInstruction(
             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);
-            // Put inside path the starting index of each decl name
-            // that we encounter as we navigate through all the field_vals
+            // 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)
@@ -925,11 +1025,35 @@ fn walkInstruction(
             }
 
             switch (tags[lhs]) {
-                else => {
-                    std.debug.panic(
-                        "TODO: handle `{s}` endings in walkInstruction.field_val",
-                        .{@tagName(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);
@@ -942,7 +1066,7 @@ fn walkInstruction(
                             .line = 0,
                             .col = 0,
                             .docs = "",
-                            .fields = null, // walkInstruction will fill `fields` if necessary
+                            .fields = null,
                         });
                         break :idx idx;
                     };
@@ -999,6 +1123,76 @@ fn walkInstruction(
         .block_inline => {
             return self.walkRef(file, parent_scope, getBlockInlineBreak(file.zir, inst_index));
         },
+        .struct_init => {
+            const pl_node = data[inst_index].pl_node;
+            const extra = file.zir.extraData(Zir.Inst.StructInit, pl_node.payload_index);
+            const field_vals = try self.arena.alloc(
+                DocData.WalkResult.Struct.FieldVal,
+                extra.data.fields_len,
+            );
+
+            var type_ref: DocData.TypeRef = undefined;
+            var idx = extra.end;
+            for (field_vals) |*fv| {
+                const init_extra = file.zir.extraData(Zir.Inst.StructInit.Item, idx);
+                idx = init_extra.end;
+
+                const field_name = blk: {
+                    const field_inst_index = init_extra.data.field_type;
+                    if (tags[field_inst_index] != .field_type) unreachable;
+                    const field_pl_node = data[field_inst_index].pl_node;
+                    const field_extra = file.zir.extraData(
+                        Zir.Inst.FieldType,
+                        field_pl_node.payload_index,
+                    );
+
+                    // On first iteration use field info to find out the struct type
+                    if (idx == extra.end) {
+                        const wr = try self.walkRef(
+                            file,
+                            parent_scope,
+                            field_extra.data.container_type,
+                        );
+                        type_ref = walkResultToTypeRef(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 };
+            }
+
+            return DocData.WalkResult{ .@"struct" = .{
+                .typeRef = type_ref,
+                .fieldVals = field_vals,
+            } };
+        },
+        .param_anytype => {
+            // Analysis of anytype function params happens in `.func`.
+            // This switch case handles the case where an expression depends
+            // on an anytype field. E.g.: `fn foo(bar: anytype) @TypeOf(bar)`.
+            // This means that we're looking at a generic expression.
+            const str_tok = data[inst_index].str_tok;
+            const name = str_tok.get(file.zir);
+            const cte_slot_index = self.comptime_exprs.items.len;
+            try self.comptime_exprs.append(self.arena, .{
+                .code = name,
+                .typeRef = .{ .type = @enumToInt(DocData.DocTypeKinds.ComptimeExpr) },
+            });
+            return DocData.WalkResult{ .comptimeExpr = cte_slot_index };
+        },
+        .param, .param_comptime => {
+            // See .param_anytype for more information.
+            const pl_tok = data[inst_index].pl_tok;
+            const extra = file.zir.extraData(Zir.Inst.Param, pl_tok.payload_index);
+            const name = file.zir.nullTerminatedString(extra.data.name);
+            const cte_slot_index = self.comptime_exprs.items.len;
+            try self.comptime_exprs.append(self.arena, .{
+                .code = name,
+                .typeRef = .{ .type = @enumToInt(DocData.DocTypeKinds.ComptimeExpr) },
+            });
+            return DocData.WalkResult{ .comptimeExpr = cte_slot_index };
+        },
         .call => {
             const pl_node = data[inst_index].pl_node;
             const extra = file.zir.extraData(Zir.Inst.Call, pl_node.payload_index);
@@ -1048,12 +1242,12 @@ fn walkInstruction(
             // TODO: handle scope rules for fn parameters
             for (fn_info.param_body[0..fn_info.total_params_len]) |param_index| {
                 switch (tags[param_index]) {
-                    else => {
-                        std.debug.panic(
-                            "TODO: handle `{s}` in walkInstruction.func\n",
-                            .{@tagName(tags[param_index])},
-                        );
-                    },
+                    else => panicWithContext(
+                        file,
+                        param_index,
+                        "TODO: handle `{s}` in walkInstruction.func\n",
+                        .{@tagName(tags[param_index])},
+                    ),
                     .param_anytype => {
                         // TODO: where are the doc comments?
                         const str_tok = data[param_index].str_tok;
@@ -2158,6 +2352,8 @@ fn walkResultToTypeRef(wr: DocData.WalkResult) DocData.TypeRef {
             .{@tagName(wr)},
         ),
 
+        .typeOf => |v| .{ .typeOf = v },
+        .comptimeExpr => |v| .{ .comptimeExpr = v },
         .declPath => |v| .{ .declPath = v },
         .type => |v| .{ .type = v },
         .call => |v| .{ .call = v },
@@ -2187,3 +2383,8 @@ fn getBlockInlineBreak(zir: Zir, inst_index: usize) Zir.Inst.Ref {
     const break_index = zir.extra[extra.end..][extra.data.body_len - 1];
     return data[break_index].@"break".operand;
 }
+
+fn panicWithContext(file: *File, inst: usize, comptime fmt: []const u8, args: anytype) noreturn {
+    std.debug.print("Context [{s}] % {}\n", .{ file.sub_file_path, inst });
+    std.debug.panic(fmt, args);
+}