authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-28 13:32:39-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-10 17:51:05-07:00
log356e653f9bd52d4aebf8ef380a4dcb9de88caf06
tree2d9bbe94edc4b54426e9b2e6810a2244ecd4a272
parent94daf873354c64602ab564e49a686ce728052b04

better to use AST than ZIR for doc generation


3 files changed, 0 insertions(+), 6495 deletions(-)

src/Autodoc.zig deleted-6035
......@@ -1,6035 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("std");
3const build_options = @import("build_options");
4const Ast = std.zig.Ast;
5const Autodoc = @This();
6const Compilation = @import("Compilation.zig");
7const Zcu = @import("Module.zig");
8const File = Zcu.File;
9const Module = @import("Package.zig").Module;
10const Tokenizer = std.zig.Tokenizer;
11const InternPool = @import("InternPool.zig");
12const Zir = std.zig.Zir;
13const Ref = Zir.Inst.Ref;
14const log = std.log.scoped(.autodoc);
15const renderer = @import("autodoc/render_source.zig");
16
17zcu: *Zcu,
18arena: std.mem.Allocator,
19
20// The goal of autodoc is to fill up these arrays
21// that will then be serialized as JSON and consumed
22// by the JS frontend.
23modules: std.AutoArrayHashMapUnmanaged(*Module, DocData.DocModule) = .{},
24files: std.AutoArrayHashMapUnmanaged(*File, usize) = .{},
25calls: std.ArrayListUnmanaged(DocData.Call) = .{},
26types: std.ArrayListUnmanaged(DocData.Type) = .{},
27decls: std.ArrayListUnmanaged(DocData.Decl) = .{},
28exprs: std.ArrayListUnmanaged(DocData.Expr) = .{},
29ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{},
30comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},
31guide_sections: std.ArrayListUnmanaged(Section) = .{},
32
33// These fields hold temporary state of the analysis process
34// and are mainly used by the decl path resolving algorithm.
35pending_ref_paths: std.AutoHashMapUnmanaged(
36 *DocData.Expr, // pointer to declpath tail end (ie `&decl_path[decl_path.len - 1]`)
37 std.ArrayListUnmanaged(RefPathResumeInfo),
38) = .{},
39ref_paths_pending_on_decls: std.AutoHashMapUnmanaged(
40 *Scope.DeclStatus,
41 std.ArrayListUnmanaged(RefPathResumeInfo),
42) = .{},
43ref_paths_pending_on_types: std.AutoHashMapUnmanaged(
44 usize,
45 std.ArrayListUnmanaged(RefPathResumeInfo),
46) = .{},
47
48/// A set of ZIR instruction refs which have a meaning other than the
49/// instruction they refer to. For instance, during analysis of the arguments to
50/// a `call`, the index of the `call` itself is repurposed to refer to the
51/// parameter type.
52/// TODO: there should be some kind of proper handling for these instructions;
53/// currently we just ignore them!
54repurposed_insts: std.AutoHashMapUnmanaged(Zir.Inst.Index, void) = .{},
55
56const RefPathResumeInfo = struct {
57 file: *File,
58 ref_path: []DocData.Expr,
59};
60
61/// Used to accumulate src_node offsets.
62/// In ZIR, all ast node indices are relative to the parent decl.
63/// More concretely, `union_decl`, `struct_decl`, `enum_decl` and `opaque_decl`
64/// and the value of each of their decls participate in the relative offset
65/// counting, and nothing else.
66/// We keep track of the line and byte values for these instructions in order
67/// to avoid tokenizing every file (on new lines) from the start every time.
68const SrcLocInfo = struct {
69 bytes: u32 = 0,
70 line: usize = 0,
71 src_node: u32 = 0,
72};
73
74const Section = struct {
75 name: []const u8 = "", // empty string is the default section
76 guides: std.ArrayListUnmanaged(Guide) = .{},
77
78 const Guide = struct {
79 name: []const u8,
80 body: []const u8,
81 };
82};
83
84pub fn generate(zcu: *Zcu, output_dir: std.fs.Dir) !void {
85 var arena_allocator = std.heap.ArenaAllocator.init(zcu.gpa);
86 defer arena_allocator.deinit();
87 var autodoc: Autodoc = .{
88 .zcu = zcu,
89 .arena = arena_allocator.allocator(),
90 };
91 try autodoc.generateZirData(output_dir);
92
93 const lib_dir = zcu.comp.zig_lib_directory.handle;
94 try lib_dir.copyFile("docs/main.js", output_dir, "main.js", .{});
95 try lib_dir.copyFile("docs/ziglexer.js", output_dir, "ziglexer.js", .{});
96 try lib_dir.copyFile("docs/commonmark.js", output_dir, "commonmark.js", .{});
97 try lib_dir.copyFile("docs/index.html", output_dir, "index.html", .{});
98}
99
100fn generateZirData(self: *Autodoc, output_dir: std.fs.Dir) !void {
101 const root_src_path = self.zcu.main_mod.root_src_path;
102 const joined_src_path = try self.zcu.main_mod.root.joinString(self.arena, root_src_path);
103 defer self.arena.free(joined_src_path);
104
105 const abs_root_src_path = try std.fs.path.resolve(self.arena, &.{ ".", joined_src_path });
106 defer self.arena.free(abs_root_src_path);
107
108 const file = self.zcu.import_table.get(abs_root_src_path).?; // file is expected to be present in the import table
109 // Append all the types in Zir.Inst.Ref.
110 {
111 comptime std.debug.assert(@intFromEnum(InternPool.Index.first_type) == 0);
112 var i: u32 = 0;
113 while (i <= @intFromEnum(InternPool.Index.last_type)) : (i += 1) {
114 const ip_index = @as(InternPool.Index, @enumFromInt(i));
115 var tmpbuf = std.ArrayList(u8).init(self.arena);
116 if (ip_index == .generic_poison_type) {
117 // Not a real type, doesn't have a normal name
118 try tmpbuf.writer().writeAll("(generic poison)");
119 } else {
120 try @import("type.zig").Type.fromInterned(ip_index).fmt(self.zcu).format("", .{}, tmpbuf.writer());
121 }
122 try self.types.append(
123 self.arena,
124 switch (ip_index) {
125 .u0_type,
126 .i0_type,
127 .u1_type,
128 .u8_type,
129 .i8_type,
130 .u16_type,
131 .i16_type,
132 .u29_type,
133 .u32_type,
134 .i32_type,
135 .u64_type,
136 .i64_type,
137 .u80_type,
138 .u128_type,
139 .i128_type,
140 .usize_type,
141 .isize_type,
142 .c_char_type,
143 .c_short_type,
144 .c_ushort_type,
145 .c_int_type,
146 .c_uint_type,
147 .c_long_type,
148 .c_ulong_type,
149 .c_longlong_type,
150 .c_ulonglong_type,
151 => .{
152 .Int = .{ .name = try tmpbuf.toOwnedSlice() },
153 },
154 .f16_type,
155 .f32_type,
156 .f64_type,
157 .f80_type,
158 .f128_type,
159 .c_longdouble_type,
160 => .{
161 .Float = .{ .name = try tmpbuf.toOwnedSlice() },
162 },
163 .comptime_int_type => .{
164 .ComptimeInt = .{ .name = try tmpbuf.toOwnedSlice() },
165 },
166 .comptime_float_type => .{
167 .ComptimeFloat = .{ .name = try tmpbuf.toOwnedSlice() },
168 },
169
170 .anyopaque_type => .{
171 .ComptimeExpr = .{ .name = try tmpbuf.toOwnedSlice() },
172 },
173
174 .bool_type => .{
175 .Bool = .{ .name = try tmpbuf.toOwnedSlice() },
176 },
177 .noreturn_type => .{
178 .NoReturn = .{ .name = try tmpbuf.toOwnedSlice() },
179 },
180 .void_type => .{
181 .Void = .{ .name = try tmpbuf.toOwnedSlice() },
182 },
183 .type_info_type => .{
184 .ComptimeExpr = .{ .name = try tmpbuf.toOwnedSlice() },
185 },
186 .type_type => .{
187 .Type = .{ .name = try tmpbuf.toOwnedSlice() },
188 },
189 .anyerror_type => .{
190 .ErrorSet = .{ .name = try tmpbuf.toOwnedSlice() },
191 },
192 // should be different types but if we don't analyze std we don't get the ast nodes etc.
193 // since they're defined in std.builtin
194 .calling_convention_type,
195 .atomic_order_type,
196 .atomic_rmw_op_type,
197 .address_space_type,
198 .float_mode_type,
199 .reduce_op_type,
200 .call_modifier_type,
201 .prefetch_options_type,
202 .export_options_type,
203 .extern_options_type,
204 => .{
205 .Type = .{ .name = try tmpbuf.toOwnedSlice() },
206 },
207 .manyptr_u8_type => .{
208 .Pointer = .{
209 .size = .Many,
210 .child = .{ .type = @intFromEnum(InternPool.Index.u8_type) },
211 .is_mutable = true,
212 },
213 },
214 .manyptr_const_u8_type => .{
215 .Pointer = .{
216 .size = .Many,
217 .child = .{ .type = @intFromEnum(InternPool.Index.u8_type) },
218 },
219 },
220 .manyptr_const_u8_sentinel_0_type => .{
221 .Pointer = .{
222 .size = .Many,
223 .child = .{ .type = @intFromEnum(InternPool.Index.u8_type) },
224 .sentinel = .{ .int = .{ .value = 0 } },
225 },
226 },
227 .single_const_pointer_to_comptime_int_type => .{
228 .Pointer = .{
229 .size = .One,
230 .child = .{ .type = @intFromEnum(InternPool.Index.comptime_int_type) },
231 },
232 },
233 .slice_const_u8_type => .{
234 .Pointer = .{
235 .size = .Slice,
236 .child = .{ .type = @intFromEnum(InternPool.Index.u8_type) },
237 },
238 },
239 .slice_const_u8_sentinel_0_type => .{
240 .Pointer = .{
241 .size = .Slice,
242 .child = .{ .type = @intFromEnum(InternPool.Index.u8_type) },
243 .sentinel = .{ .int = .{ .value = 0 } },
244 },
245 },
246 // Not fully correct
247 // since it actually has no src or line_number
248 .empty_struct_type => .{
249 .Struct = .{
250 .name = "",
251 .src = 0,
252 .is_tuple = false,
253 .line_number = 0,
254 .parent_container = null,
255 .layout = null,
256 },
257 },
258 .anyerror_void_error_union_type => .{
259 .ErrorUnion = .{
260 .lhs = .{ .type = @intFromEnum(InternPool.Index.anyerror_type) },
261 .rhs = .{ .type = @intFromEnum(InternPool.Index.void_type) },
262 },
263 },
264 .anyframe_type => .{
265 .AnyFrame = .{ .name = try tmpbuf.toOwnedSlice() },
266 },
267 .enum_literal_type => .{
268 .EnumLiteral = .{ .name = try tmpbuf.toOwnedSlice() },
269 },
270 .undefined_type => .{
271 .Undefined = .{ .name = try tmpbuf.toOwnedSlice() },
272 },
273 .null_type => .{
274 .Null = .{ .name = try tmpbuf.toOwnedSlice() },
275 },
276 .optional_noreturn_type => .{
277 .Optional = .{
278 .name = try tmpbuf.toOwnedSlice(),
279 .child = .{ .type = @intFromEnum(InternPool.Index.noreturn_type) },
280 },
281 },
282 // Poison and special tag
283 .generic_poison_type,
284 .var_args_param_type,
285 .adhoc_inferred_error_set_type,
286 => .{
287 .Type = .{ .name = try tmpbuf.toOwnedSlice() },
288 },
289 // We want to catch new types added to InternPool.Index
290 else => unreachable,
291 },
292 );
293 }
294 }
295
296 const rootName = blk: {
297 const rootName = std.fs.path.basename(self.zcu.main_mod.root_src_path);
298 break :blk rootName[0 .. rootName.len - 4];
299 };
300
301 const main_type_index = self.types.items.len;
302 {
303 try self.modules.put(self.arena, self.zcu.main_mod, .{
304 .name = rootName,
305 .main = main_type_index,
306 .table = .{},
307 });
308 try self.modules.entries.items(.value)[0].table.put(
309 self.arena,
310 self.zcu.main_mod,
311 .{
312 .name = rootName,
313 .value = 0,
314 },
315 );
316 }
317
318 var root_scope = Scope{
319 .parent = null,
320 .enclosing_type = null,
321 };
322
323 const tldoc_comment = try self.getTLDocComment(file);
324 const cleaned_tldoc_comment = try self.findGuidePaths(file, tldoc_comment);
325 defer self.arena.free(cleaned_tldoc_comment);
326 try self.ast_nodes.append(self.arena, .{
327 .name = "(root)",
328 .docs = cleaned_tldoc_comment,
329 });
330 try self.files.put(self.arena, file, main_type_index);
331
332 _ = try self.walkInstruction(
333 file,
334 &root_scope,
335 .{},
336 .main_struct_inst,
337 false,
338 null,
339 );
340
341 if (self.ref_paths_pending_on_decls.count() > 0) {
342 @panic("some decl paths were never fully analyzed (pending on decls)");
343 }
344
345 if (self.ref_paths_pending_on_types.count() > 0) {
346 @panic("some decl paths were never fully analyzed (pending on types)");
347 }
348
349 if (self.pending_ref_paths.count() > 0) {
350 @panic("some decl paths were never fully analyzed");
351 }
352
353 var data = DocData{
354 .modules = self.modules,
355 .files = self.files,
356 .calls = self.calls.items,
357 .types = self.types.items,
358 .decls = self.decls.items,
359 .exprs = self.exprs.items,
360 .astNodes = self.ast_nodes.items,
361 .comptimeExprs = self.comptime_exprs.items,
362 .guideSections = self.guide_sections,
363 };
364
365 inline for (comptime std.meta.tags(std.meta.FieldEnum(DocData))) |f| {
366 const field_name = @tagName(f);
367 const file_name = "data-" ++ field_name ++ ".js";
368 const data_js_f = try output_dir.createFile(file_name, .{});
369 defer data_js_f.close();
370
371 var buffer = std.io.bufferedWriter(data_js_f.writer());
372 const out = buffer.writer();
373
374 try out.print("var {s} =", .{field_name});
375
376 var jsw = std.json.writeStream(out, .{
377 .whitespace = .minified,
378 .emit_null_optional_fields = true,
379 });
380
381 switch (f) {
382 .files => try writeFileTableToJson(data.files, data.modules, &jsw),
383 .guideSections => try writeGuidesToJson(data.guideSections, &jsw),
384 .modules => try jsw.write(data.modules.values()),
385 else => try jsw.write(@field(data, field_name)),
386 }
387
388 // try std.json.stringifyArbitraryDepth(
389 // self.arena,
390 // @field(data, field.name),
391 // .{
392 // .whitespace = .minified,
393 // .emit_null_optional_fields = true,
394 // },
395 // out,
396 // );
397 try out.print(";", .{});
398
399 // last thing (that can fail) that we do is flush
400 try buffer.flush();
401 }
402
403 {
404 output_dir.makeDir("src") catch |e| switch (e) {
405 error.PathAlreadyExists => {},
406 else => |err| return err,
407 };
408 const html_dir = try output_dir.openDir("src", .{});
409
410 var files_iterator = self.files.iterator();
411
412 while (files_iterator.next()) |entry| {
413 const sub_file_path = entry.key_ptr.*.sub_file_path;
414 const file_module = entry.key_ptr.*.mod;
415 const module_name = (self.modules.get(file_module) orelse continue).name;
416
417 const file_path = std.fs.path.dirname(sub_file_path) orelse "";
418 const file_name = if (file_path.len > 0) sub_file_path[file_path.len + 1 ..] else sub_file_path;
419
420 const html_file_name = try std.mem.concat(self.arena, u8, &.{ file_name, ".html" });
421 defer self.arena.free(html_file_name);
422
423 const dir_name = try std.fs.path.join(self.arena, &.{ module_name, file_path });
424 defer self.arena.free(dir_name);
425
426 var dir = try html_dir.makeOpenPath(dir_name, .{});
427 defer dir.close();
428
429 const html_file = dir.createFile(html_file_name, .{}) catch |err| switch (err) {
430 error.PathAlreadyExists => try dir.openFile(html_file_name, .{}),
431 else => return err,
432 };
433 defer html_file.close();
434 var buffer = std.io.bufferedWriter(html_file.writer());
435
436 const out = buffer.writer();
437
438 try renderer.genHtml(self.zcu.gpa, entry.key_ptr.*, out);
439 try buffer.flush();
440 }
441 }
442}
443
444/// Represents a chain of scopes, used to resolve decl references to the
445/// corresponding entry in `self.decls`. It also keeps track of whether
446/// a given decl has been analyzed or not.
447const Scope = struct {
448 parent: ?*Scope,
449 map: std.AutoHashMapUnmanaged(
450 Zir.NullTerminatedString, // index into the current file's string table (decl name)
451 *DeclStatus,
452 ) = .{},
453 captures: []const Zir.Inst.Capture = &.{},
454 enclosing_type: ?usize, // index into `types`, null = file top-level struct
455
456 pub const DeclStatus = union(enum) {
457 Analyzed: usize, // index into `decls`
458 Pending,
459 NotRequested: u32, // instr_index
460 };
461
462 fn getCapture(scope: Scope, idx: u16) struct {
463 union(enum) { inst: Zir.Inst.Index, decl: Zir.NullTerminatedString },
464 *Scope,
465 } {
466 const parent = scope.parent.?;
467 return switch (scope.captures[idx].unwrap()) {
468 .nested => |parent_idx| parent.getCapture(parent_idx),
469 .instruction => |inst| .{
470 .{ .inst = inst },
471 parent,
472 },
473 .decl_val, .decl_ref => |str| .{
474 .{ .decl = str },
475 parent,
476 },
477 };
478 }
479
480 /// Returns a pointer so that the caller has a chance to modify the value
481 /// in case they decide to start analyzing a previously not requested decl.
482 /// Another reason is that in some places we use the pointer to uniquely
483 /// refer to a decl, as we wait for it to be analyzed. This means that
484 /// those pointers must stay stable.
485 pub fn resolveDeclName(self: Scope, string_table_idx: Zir.NullTerminatedString, file: *File, inst: Zir.Inst.OptionalIndex) *DeclStatus {
486 var cur: ?*const Scope = &self;
487 return while (cur) |s| : (cur = s.parent) {
488 break s.map.get(string_table_idx) orelse continue;
489 } else {
490 printWithOptionalContext(
491 file,
492 inst,
493 "Could not find `{s}`\n\n",
494 .{file.zir.nullTerminatedString(string_table_idx)},
495 );
496 unreachable;
497 };
498 }
499
500 pub fn insertDeclRef(
501 self: *Scope,
502 arena: std.mem.Allocator,
503 decl_name_index: Zir.NullTerminatedString, // index into the current file's string table
504 decl_status: DeclStatus,
505 ) !void {
506 const decl_status_ptr = try arena.create(DeclStatus);
507 errdefer arena.destroy(decl_status_ptr);
508
509 decl_status_ptr.* = decl_status;
510 try self.map.put(arena, decl_name_index, decl_status_ptr);
511 }
512};
513
514/// The output of our analysis process.
515const DocData = struct {
516 // NOTE: editing fields of DocData requires also updating:
517 // - the deployment script for ziglang.org
518 // - imports in index.html
519 typeKinds: []const []const u8 = std.meta.fieldNames(DocTypeKinds),
520 rootMod: u32 = 0,
521 modules: std.AutoArrayHashMapUnmanaged(*Module, DocModule),
522
523 // non-hardcoded stuff
524 astNodes: []AstNode,
525 calls: []Call,
526 files: std.AutoArrayHashMapUnmanaged(*File, usize),
527 types: []Type,
528 decls: []Decl,
529 exprs: []Expr,
530 comptimeExprs: []ComptimeExpr,
531
532 guideSections: std.ArrayListUnmanaged(Section),
533
534 const Call = struct {
535 func: Expr,
536 args: []Expr,
537 ret: Expr,
538 };
539
540 /// All the type "families" as described by `std.builtin.TypeId`
541 /// plus a couple extra that are unique to our use case.
542 ///
543 /// `Unanalyzed` is used so that we can refer to types that have started
544 /// analysis but that haven't been fully analyzed yet (in case we find
545 /// self-referential stuff, like `@This()`).
546 ///
547 /// `ComptimeExpr` represents the result of a piece of comptime logic
548 /// that we weren't able to analyze fully. Examples of that are comptime
549 /// function calls and comptime if / switch / ... expressions.
550 const DocTypeKinds = @typeInfo(Type).Union.tag_type.?;
551
552 const ComptimeExpr = struct {
553 code: []const u8,
554 };
555 const DocModule = struct {
556 name: []const u8 = "(root)",
557 file: usize = 0, // index into `files`
558 main: usize = 0, // index into `types`
559 table: std.AutoHashMapUnmanaged(*Module, TableEntry),
560 pub const TableEntry = struct {
561 name: []const u8,
562 value: usize,
563 };
564
565 pub fn jsonStringify(self: DocModule, jsw: anytype) !void {
566 try jsw.beginObject();
567 inline for (comptime std.meta.tags(std.meta.FieldEnum(DocModule))) |f| {
568 const f_name = @tagName(f);
569 try jsw.objectField(f_name);
570 switch (f) {
571 .table => try writeModuleTableToJson(self.table, jsw),
572 else => try jsw.write(@field(self, f_name)),
573 }
574 }
575 try jsw.endObject();
576 }
577 };
578
579 const Decl = struct {
580 name: []const u8,
581 kind: []const u8,
582 src: usize, // index into astNodes
583 value: WalkResult,
584 // The index in astNodes of the `test declname { }` node
585 decltest: ?usize = null,
586 is_uns: bool = false, // usingnamespace
587 parent_container: ?usize, // index into `types`
588
589 pub fn jsonStringify(self: Decl, jsw: anytype) !void {
590 try jsw.beginArray();
591 inline for (comptime std.meta.fields(Decl)) |f| {
592 try jsw.write(@field(self, f.name));
593 }
594 try jsw.endArray();
595 }
596 };
597
598 const AstNode = struct {
599 file: usize = 0, // index into files
600 line: usize = 0,
601 col: usize = 0,
602 name: ?[]const u8 = null,
603 code: ?[]const u8 = null,
604 docs: ?[]const u8 = null,
605 fields: ?[]usize = null, // index into astNodes
606 @"comptime": bool = false,
607
608 pub fn jsonStringify(self: AstNode, jsw: anytype) !void {
609 try jsw.beginArray();
610 inline for (comptime std.meta.fields(AstNode)) |f| {
611 try jsw.write(@field(self, f.name));
612 }
613 try jsw.endArray();
614 }
615 };
616
617 const Type = union(enum) {
618 Unanalyzed: struct {},
619 Type: struct { name: []const u8 },
620 Void: struct { name: []const u8 },
621 Bool: struct { name: []const u8 },
622 NoReturn: struct { name: []const u8 },
623 Int: struct { name: []const u8 },
624 Float: struct { name: []const u8 },
625 Pointer: struct {
626 size: std.builtin.Type.Pointer.Size,
627 child: Expr,
628 sentinel: ?Expr = null,
629 @"align": ?Expr = null,
630 address_space: ?Expr = null,
631 bit_start: ?Expr = null,
632 host_size: ?Expr = null,
633 is_ref: bool = false,
634 is_allowzero: bool = false,
635 is_mutable: bool = false,
636 is_volatile: bool = false,
637 has_sentinel: bool = false,
638 has_align: bool = false,
639 has_addrspace: bool = false,
640 has_bit_range: bool = false,
641 },
642 Array: struct {
643 len: Expr,
644 child: Expr,
645 sentinel: ?Expr = null,
646 },
647 Struct: struct {
648 name: []const u8,
649 src: usize, // index into astNodes
650 privDecls: []usize = &.{}, // index into decls
651 pubDecls: []usize = &.{}, // index into decls
652 field_types: []Expr = &.{}, // (use src->fields to find names)
653 field_defaults: []?Expr = &.{}, // default values is specified
654 backing_int: ?Expr = null, // backing integer if specified
655 is_tuple: bool,
656 line_number: usize,
657 parent_container: ?usize, // index into `types`
658 layout: ?Expr, // if different than Auto
659 },
660 ComptimeExpr: struct { name: []const u8 },
661 ComptimeFloat: struct { name: []const u8 },
662 ComptimeInt: struct { name: []const u8 },
663 Undefined: struct { name: []const u8 },
664 Null: struct { name: []const u8 },
665 Optional: struct {
666 name: []const u8,
667 child: Expr,
668 },
669 ErrorUnion: struct { lhs: Expr, rhs: Expr },
670 InferredErrorUnion: struct { payload: Expr },
671 ErrorSet: struct {
672 name: []const u8,
673 fields: ?[]const Field = null,
674 // TODO: fn field for inferred error sets?
675 },
676 Enum: struct {
677 name: []const u8,
678 src: usize, // index into astNodes
679 privDecls: []usize = &.{}, // index into decls
680 pubDecls: []usize = &.{}, // index into decls
681 // (use src->fields to find field names)
682 tag: ?Expr = null, // tag type if specified
683 values: []?Expr = &.{}, // tag values if specified
684 nonexhaustive: bool,
685 parent_container: ?usize, // index into `types`
686 },
687 Union: struct {
688 name: []const u8,
689 src: usize, // index into astNodes
690 privDecls: []usize = &.{}, // index into decls
691 pubDecls: []usize = &.{}, // index into decls
692 fields: []Expr = &.{}, // (use src->fields to find names)
693 tag: ?Expr, // tag type if specified
694 auto_enum: bool, // tag is an auto enum
695 parent_container: ?usize, // index into `types`
696 layout: ?Expr, // if different than Auto
697 },
698 Fn: struct {
699 name: []const u8,
700 src: ?usize = null, // index into `astNodes`
701 ret: Expr,
702 generic_ret: ?Expr = null,
703 params: ?[]Expr = null, // (use src->fields to find names)
704 lib_name: []const u8 = "",
705 is_var_args: bool = false,
706 is_inferred_error: bool = false,
707 has_lib_name: bool = false,
708 has_cc: bool = false,
709 cc: ?usize = null,
710 @"align": ?usize = null,
711 has_align: bool = false,
712 is_test: bool = false,
713 is_extern: bool = false,
714 },
715 Opaque: struct {
716 name: []const u8,
717 src: usize, // index into astNodes
718 privDecls: []usize = &.{}, // index into decls
719 pubDecls: []usize = &.{}, // index into decls
720 parent_container: ?usize, // index into `types`
721 },
722 Frame: struct { name: []const u8 },
723 AnyFrame: struct { name: []const u8 },
724 Vector: struct { name: []const u8 },
725 EnumLiteral: struct { name: []const u8 },
726
727 const Field = struct {
728 name: []const u8,
729 docs: []const u8,
730 };
731
732 pub fn jsonStringify(self: Type, jsw: anytype) !void {
733 const active_tag = std.meta.activeTag(self);
734 try jsw.beginArray();
735 try jsw.write(@intFromEnum(active_tag));
736 inline for (comptime std.meta.fields(Type)) |case| {
737 if (@field(Type, case.name) == active_tag) {
738 const current_value = @field(self, case.name);
739 inline for (comptime std.meta.fields(case.type)) |f| {
740 if (f.type == std.builtin.Type.Pointer.Size) {
741 try jsw.write(@intFromEnum(@field(current_value, f.name)));
742 } else {
743 try jsw.write(@field(current_value, f.name));
744 }
745 }
746 }
747 }
748 try jsw.endArray();
749 }
750 };
751
752 /// An Expr represents the (untyped) result of analyzing instructions.
753 /// The data is normalized, which means that an Expr that results in a
754 /// type definition will hold an index into `self.types`.
755 pub const Expr = union(enum) {
756 comptimeExpr: usize, // index in `comptimeExprs`
757 void: struct {},
758 @"unreachable": struct {},
759 null: struct {},
760 undefined: struct {},
761 @"struct": []FieldVal,
762 fieldVal: FieldVal,
763 bool: bool,
764 @"anytype": struct {},
765 @"&": usize, // index in `exprs`
766 type: usize, // index in `types`
767 this: usize, // index in `types`
768 declRef: *Scope.DeclStatus,
769 declIndex: usize, // index into `decls`, alternative repr for `declRef`
770 declName: []const u8, // unresolved decl name
771 builtinField: enum { len, ptr },
772 fieldRef: FieldRef,
773 refPath: []Expr,
774 int: struct {
775 value: u64, // direct value
776 negated: bool = false,
777 },
778 int_big: struct {
779 value: []const u8, // string representation
780 negated: bool = false,
781 },
782 float: f64, // direct value
783 float128: f128, // direct value
784 array: []usize, // index in `exprs`
785 call: usize, // index in `calls`
786 enumLiteral: []const u8, // direct value
787 typeOf: usize, // index in `exprs`
788 typeOf_peer: []usize,
789 errorUnion: usize, // index in `types`
790 as: As,
791 sizeOf: usize, // index in `exprs`
792 bitSizeOf: usize, // index in `exprs`
793 compileError: usize, // index in `exprs`
794 optionalPayload: usize, // index in `exprs`
795 elemVal: ElemVal,
796 errorSets: usize,
797 string: []const u8, // direct value
798 sliceIndex: usize,
799 slice: Slice,
800 sliceLength: SliceLength,
801 cmpxchgIndex: usize,
802 cmpxchg: Cmpxchg,
803 builtin: Builtin,
804 builtinIndex: usize,
805 builtinBin: BuiltinBin,
806 builtinBinIndex: usize,
807 unionInit: UnionInit,
808 builtinCall: BuiltinCall,
809 mulAdd: MulAdd,
810 switchIndex: usize, // index in `exprs`
811 switchOp: SwitchOp,
812 unOp: UnOp,
813 unOpIndex: usize,
814 binOp: BinOp,
815 binOpIndex: usize,
816 load: usize, // index in `exprs`
817 const UnOp = struct {
818 param: usize, // index in `exprs`
819 name: []const u8 = "", // tag name
820 };
821 const BinOp = struct {
822 lhs: usize, // index in `exprs`
823 rhs: usize, // index in `exprs`
824 name: []const u8 = "", // tag name
825 };
826 const SwitchOp = struct {
827 cond_index: usize,
828 file_name: []const u8,
829 src: usize,
830 outer_decl: usize, // index in `types`
831 };
832 const BuiltinBin = struct {
833 name: []const u8 = "", // fn name
834 lhs: usize, // index in `exprs`
835 rhs: usize, // index in `exprs`
836 };
837 const UnionInit = struct {
838 type: usize, // index in `exprs`
839 field: usize, // index in `exprs`
840 init: usize, // index in `exprs`
841 };
842 const Builtin = struct {
843 name: []const u8 = "", // fn name
844 param: usize, // index in `exprs`
845 };
846 const BuiltinCall = struct {
847 modifier: usize, // index in `exprs`
848 function: usize, // index in `exprs`
849 args: usize, // index in `exprs`
850 };
851 const MulAdd = struct {
852 mulend1: usize, // index in `exprs`
853 mulend2: usize, // index in `exprs`
854 addend: usize, // index in `exprs`
855 type: usize, // index in `exprs`
856 };
857 const Slice = struct {
858 lhs: usize, // index in `exprs`
859 start: usize,
860 end: ?usize = null,
861 sentinel: ?usize = null, // index in `exprs`
862 };
863 const SliceLength = struct {
864 lhs: usize,
865 start: usize,
866 len: usize,
867 sentinel: ?usize = null,
868 };
869 const Cmpxchg = struct {
870 name: []const u8,
871 type: usize,
872 ptr: usize,
873 expected_value: usize,
874 new_value: usize,
875 success_order: usize,
876 failure_order: usize,
877 };
878 const As = struct {
879 typeRefArg: ?usize, // index in `exprs`
880 exprArg: usize, // index in `exprs`
881 };
882 const FieldRef = struct {
883 type: usize, // index in `types`
884 index: usize, // index in type.fields
885 };
886
887 const FieldVal = struct {
888 name: []const u8,
889 val: struct {
890 typeRef: ?usize, // index in `exprs`
891 expr: usize, // index in `exprs`
892 },
893 };
894
895 const ElemVal = struct {
896 lhs: usize, // index in `exprs`
897 rhs: usize, // index in `exprs`
898 };
899
900 pub fn jsonStringify(self: Expr, jsw: anytype) !void {
901 const active_tag = std.meta.activeTag(self);
902 try jsw.beginObject();
903 if (active_tag == .declIndex) {
904 try jsw.objectField("declRef");
905 } else {
906 try jsw.objectField(@tagName(active_tag));
907 }
908 switch (self) {
909 .int => {
910 if (self.int.negated) {
911 try jsw.write(-@as(i65, self.int.value));
912 } else {
913 try jsw.write(self.int.value);
914 }
915 },
916 .builtinField => {
917 try jsw.write(@tagName(self.builtinField));
918 },
919 .declRef => {
920 try jsw.write(self.declRef.Analyzed);
921 },
922 else => {
923 inline for (comptime std.meta.fields(Expr)) |case| {
924 // TODO: this is super ugly, fix once `inline else` is a thing
925 if (comptime std.mem.eql(u8, case.name, "builtinField"))
926 continue;
927 if (comptime std.mem.eql(u8, case.name, "declRef"))
928 continue;
929 if (@field(Expr, case.name) == active_tag) {
930 try jsw.write(@field(self, case.name));
931 }
932 }
933 },
934 }
935 try jsw.endObject();
936 }
937 };
938
939 /// A WalkResult represents the result of the analysis process done to a
940 /// a Zir instruction. Walk results carry type information either inferred
941 /// from the context (eg string literals are pointers to null-terminated
942 /// arrays), or because of @as() instructions.
943 /// Since the type information is only needed in certain contexts, the
944 /// underlying normalized data (Expr) is untyped.
945 const WalkResult = struct {
946 typeRef: ?Expr = null,
947 expr: Expr,
948 };
949};
950
951const AutodocErrors = error{
952 OutOfMemory,
953 CurrentWorkingDirectoryUnlinked,
954 UnexpectedEndOfFile,
955 ModuleNotFound,
956 ImportOutsideModulePath,
957} || std.fs.File.OpenError || std.fs.File.ReadError;
958
959/// `call` instructions will have loopy references to themselves
960/// whenever an as_node is required for a complex expression.
961/// This type is used to keep track of dangerous instruction
962/// numbers that we definitely don't want to recurse into.
963const CallContext = struct {
964 inst: Zir.Inst.Index,
965 prev: ?*const CallContext,
966};
967
968/// Called when we need to analyze a Zir instruction.
969/// For example it gets called by `generateZirData` on instruction 0,
970/// which represents the top-level struct corresponding to the root file.
971/// Note that in some situations where we're analyzing code that only allows
972/// for a limited subset of Zig syntax, we don't always resort to calling
973/// `walkInstruction` and instead sometimes we handle Zir directly.
974/// The best example of that are instructions corresponding to function
975/// params, as those can only occur while analyzing a function definition.
976fn walkInstruction(
977 self: *Autodoc,
978 file: *File,
979 parent_scope: *Scope,
980 parent_src: SrcLocInfo,
981 inst: Zir.Inst.Index,
982 need_type: bool, // true if the caller needs us to provide also a typeRef
983 call_ctx: ?*const CallContext,
984) AutodocErrors!DocData.WalkResult {
985 const tags = file.zir.instructions.items(.tag);
986 const data = file.zir.instructions.items(.data);
987
988 if (self.repurposed_insts.contains(inst)) {
989 // TODO: better handling here
990 return .{ .expr = .{ .comptimeExpr = 0 } };
991 }
992
993 // We assume that the topmost ast_node entry corresponds to our decl
994 const self_ast_node_index = self.ast_nodes.items.len - 1;
995
996 switch (tags[@intFromEnum(inst)]) {
997 else => {
998 printWithContext(
999 file,
1000 inst,
1001 "TODO: implement `{s}` for walkInstruction\n\n",
1002 .{@tagName(tags[@intFromEnum(inst)])},
1003 );
1004 return self.cteTodo(@tagName(tags[@intFromEnum(inst)]));
1005 },
1006 .import => {
1007 const str_tok = data[@intFromEnum(inst)].str_tok;
1008 const path = str_tok.get(file.zir);
1009
1010 // importFile cannot error out since all files
1011 // are already loaded at this point
1012 if (file.mod.deps.get(path)) |other_module| {
1013 const result = try self.modules.getOrPut(self.arena, other_module);
1014
1015 // Immediately add this module to the import table of our
1016 // current module, regardless of wether it's new or not.
1017 if (self.modules.getPtr(file.mod)) |current_module| {
1018 // TODO: apparently, in the stdlib a file gets analyzed before
1019 // its module gets added. I guess we're importing a file
1020 // that belongs to another module through its file path?
1021 // (ie not through its module name).
1022 // We're bailing for now, but maybe we shouldn't?
1023 _ = try current_module.table.getOrPutValue(
1024 self.arena,
1025 other_module,
1026 .{
1027 .name = path,
1028 .value = self.modules.getIndex(other_module).?,
1029 },
1030 );
1031 }
1032
1033 if (result.found_existing) {
1034 return DocData.WalkResult{
1035 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
1036 .expr = .{ .type = result.value_ptr.main },
1037 };
1038 }
1039
1040 // create a new module entry
1041 const main_type_index = self.types.items.len;
1042 result.value_ptr.* = .{
1043 .name = path,
1044 .main = main_type_index,
1045 .table = .{},
1046 };
1047
1048 // TODO: Add this module as a dependency to the current module
1049 // TODO: this seems something that could be done in bulk
1050 // at the beginning or the end, or something.
1051 const abs_root_src_path = try std.fs.path.resolve(self.arena, &.{
1052 ".",
1053 other_module.root.root_dir.path orelse ".",
1054 other_module.root.sub_path,
1055 other_module.root_src_path,
1056 });
1057 defer self.arena.free(abs_root_src_path);
1058
1059 const new_file = self.zcu.import_table.get(abs_root_src_path).?;
1060
1061 var root_scope = Scope{
1062 .parent = null,
1063 .enclosing_type = null,
1064 };
1065 const maybe_tldoc_comment = try self.getTLDocComment(file);
1066 try self.ast_nodes.append(self.arena, .{
1067 .name = "(root)",
1068 .docs = maybe_tldoc_comment,
1069 });
1070 try self.files.put(self.arena, new_file, main_type_index);
1071 return self.walkInstruction(
1072 new_file,
1073 &root_scope,
1074 .{},
1075 .main_struct_inst,
1076 false,
1077 call_ctx,
1078 );
1079 }
1080
1081 const new_file = try self.zcu.importFile(file, path);
1082 const result = try self.files.getOrPut(self.arena, new_file.file);
1083 if (result.found_existing) {
1084 return DocData.WalkResult{
1085 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
1086 .expr = .{ .type = result.value_ptr.* },
1087 };
1088 }
1089
1090 const maybe_tldoc_comment = try self.getTLDocComment(new_file.file);
1091 try self.ast_nodes.append(self.arena, .{
1092 .name = path,
1093 .docs = maybe_tldoc_comment,
1094 });
1095
1096 result.value_ptr.* = self.types.items.len;
1097
1098 var new_scope = Scope{
1099 .parent = null,
1100 .enclosing_type = null,
1101 };
1102
1103 return self.walkInstruction(
1104 new_file.file,
1105 &new_scope,
1106 .{},
1107 .main_struct_inst,
1108 need_type,
1109 call_ctx,
1110 );
1111 },
1112 .ret_type => {
1113 return DocData.WalkResult{
1114 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
1115 .expr = .{ .type = @intFromEnum(Ref.type_type) },
1116 };
1117 },
1118 .ret_node => {
1119 const un_node = data[@intFromEnum(inst)].un_node;
1120 return self.walkRef(
1121 file,
1122 parent_scope,
1123 parent_src,
1124 un_node.operand,
1125 false,
1126 call_ctx,
1127 );
1128 },
1129 .ret_load => {
1130 const un_node = data[@intFromEnum(inst)].un_node;
1131 const res_ptr_ref = un_node.operand;
1132 const res_ptr_inst = @intFromEnum(res_ptr_ref.toIndex().?);
1133 // TODO: this instruction doesn't let us know trivially if there's
1134 // branching involved or not. For now here's the strat:
1135 // We search backwarts until `ret_ptr` for `store_node`,
1136 // if we find only one, then that's our value, if we find more
1137 // than one, then it means that there's branching involved.
1138 // Maybe.
1139
1140 var i = @intFromEnum(inst) - 1;
1141 var result_ref: ?Ref = null;
1142 while (i > res_ptr_inst) : (i -= 1) {
1143 if (tags[i] == .store_node) {
1144 const pl_node = data[i].pl_node;
1145 const extra = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index);
1146 if (extra.data.lhs == res_ptr_ref) {
1147 // this store_load instruction is indeed pointing at
1148 // the result location that we care about!
1149 if (result_ref != null) return DocData.WalkResult{
1150 .expr = .{ .comptimeExpr = 0 },
1151 };
1152 result_ref = extra.data.rhs;
1153 }
1154 }
1155 }
1156
1157 if (result_ref) |rr| {
1158 return self.walkRef(
1159 file,
1160 parent_scope,
1161 parent_src,
1162 rr,
1163 need_type,
1164 call_ctx,
1165 );
1166 }
1167
1168 return DocData.WalkResult{
1169 .expr = .{ .comptimeExpr = 0 },
1170 };
1171 },
1172 .str => {
1173 const str = data[@intFromEnum(inst)].str.get(file.zir);
1174
1175 const tRef: ?DocData.Expr = if (!need_type) null else blk: {
1176 const arrTypeId = self.types.items.len;
1177 try self.types.append(self.arena, .{
1178 .Array = .{
1179 .len = .{ .int = .{ .value = str.len } },
1180 .child = .{ .type = @intFromEnum(Ref.u8_type) },
1181 .sentinel = .{ .int = .{
1182 .value = 0,
1183 .negated = false,
1184 } },
1185 },
1186 });
1187 // const sentinel: ?usize = if (ptr.flags.has_sentinel) 0 else null;
1188 const ptrTypeId = self.types.items.len;
1189 try self.types.append(self.arena, .{
1190 .Pointer = .{
1191 .size = .One,
1192 .child = .{ .type = arrTypeId },
1193 .sentinel = .{ .int = .{
1194 .value = 0,
1195 .negated = false,
1196 } },
1197 .is_mutable = false,
1198 },
1199 });
1200 break :blk .{ .type = ptrTypeId };
1201 };
1202
1203 return DocData.WalkResult{
1204 .typeRef = tRef,
1205 .expr = .{ .string = str },
1206 };
1207 },
1208 .compile_error => {
1209 const un_node = data[@intFromEnum(inst)].un_node;
1210
1211 const operand: DocData.WalkResult = try self.walkRef(
1212 file,
1213 parent_scope,
1214 parent_src,
1215 un_node.operand,
1216 false,
1217 call_ctx,
1218 );
1219
1220 const operand_index = self.exprs.items.len;
1221 try self.exprs.append(self.arena, operand.expr);
1222
1223 return DocData.WalkResult{
1224 .expr = .{ .compileError = operand_index },
1225 };
1226 },
1227 .enum_literal => {
1228 const str_tok = data[@intFromEnum(inst)].str_tok;
1229 const literal = file.zir.nullTerminatedString(str_tok.start);
1230 const type_index = self.types.items.len;
1231 try self.types.append(self.arena, .{
1232 .EnumLiteral = .{ .name = "todo enum literal" },
1233 });
1234
1235 return DocData.WalkResult{
1236 .typeRef = .{ .type = type_index },
1237 .expr = .{ .enumLiteral = literal },
1238 };
1239 },
1240 .int => {
1241 const int = data[@intFromEnum(inst)].int;
1242 return DocData.WalkResult{
1243 .typeRef = .{ .type = @intFromEnum(Ref.comptime_int_type) },
1244 .expr = .{ .int = .{ .value = int } },
1245 };
1246 },
1247 .int_big => {
1248 // @check
1249 const str = data[@intFromEnum(inst)].str; //.get(file.zir);
1250 const byte_count = str.len * @sizeOf(std.math.big.Limb);
1251 const limb_bytes = file.zir.string_bytes[@intFromEnum(str.start)..][0..byte_count];
1252
1253 const limbs = try self.arena.alloc(std.math.big.Limb, str.len);
1254 @memcpy(std.mem.sliceAsBytes(limbs)[0..limb_bytes.len], limb_bytes);
1255
1256 const big_int = std.math.big.int.Const{
1257 .limbs = limbs,
1258 .positive = true,
1259 };
1260
1261 const as_string = try big_int.toStringAlloc(self.arena, 10, .lower);
1262
1263 return DocData.WalkResult{
1264 .typeRef = .{ .type = @intFromEnum(Ref.comptime_int_type) },
1265 .expr = .{ .int_big = .{ .value = as_string } },
1266 };
1267 },
1268 .@"unreachable" => {
1269 return DocData.WalkResult{
1270 .typeRef = .{ .type = @intFromEnum(Ref.noreturn_type) },
1271 .expr = .{ .@"unreachable" = .{} },
1272 };
1273 },
1274
1275 .slice_start => {
1276 const pl_node = data[@intFromEnum(inst)].pl_node;
1277 const extra = file.zir.extraData(Zir.Inst.SliceStart, pl_node.payload_index);
1278
1279 const slice_index = self.exprs.items.len;
1280 try self.exprs.append(self.arena, .{ .slice = .{ .lhs = 0, .start = 0 } });
1281
1282 const lhs: DocData.WalkResult = try self.walkRef(
1283 file,
1284 parent_scope,
1285 parent_src,
1286 extra.data.lhs,
1287 false,
1288 call_ctx,
1289 );
1290 const start: DocData.WalkResult = try self.walkRef(
1291 file,
1292 parent_scope,
1293 parent_src,
1294 extra.data.start,
1295 false,
1296 call_ctx,
1297 );
1298
1299 const lhs_index = self.exprs.items.len;
1300 try self.exprs.append(self.arena, lhs.expr);
1301 const start_index = self.exprs.items.len;
1302 try self.exprs.append(self.arena, start.expr);
1303 self.exprs.items[slice_index] = .{ .slice = .{ .lhs = lhs_index, .start = start_index } };
1304
1305 const typeRef = switch (lhs.expr) {
1306 .declRef => |ref| self.decls.items[ref.Analyzed].value.typeRef,
1307 else => null,
1308 };
1309
1310 return DocData.WalkResult{
1311 .typeRef = typeRef,
1312 .expr = .{ .sliceIndex = slice_index },
1313 };
1314 },
1315 .slice_end => {
1316 const pl_node = data[@intFromEnum(inst)].pl_node;
1317 const extra = file.zir.extraData(Zir.Inst.SliceEnd, pl_node.payload_index);
1318
1319 const slice_index = self.exprs.items.len;
1320 try self.exprs.append(self.arena, .{ .slice = .{ .lhs = 0, .start = 0 } });
1321
1322 const lhs: DocData.WalkResult = try self.walkRef(
1323 file,
1324 parent_scope,
1325 parent_src,
1326 extra.data.lhs,
1327 false,
1328 call_ctx,
1329 );
1330 const start: DocData.WalkResult = try self.walkRef(
1331 file,
1332 parent_scope,
1333 parent_src,
1334 extra.data.start,
1335 false,
1336 call_ctx,
1337 );
1338 const end: DocData.WalkResult = try self.walkRef(
1339 file,
1340 parent_scope,
1341 parent_src,
1342 extra.data.end,
1343 false,
1344 call_ctx,
1345 );
1346
1347 const lhs_index = self.exprs.items.len;
1348 try self.exprs.append(self.arena, lhs.expr);
1349 const start_index = self.exprs.items.len;
1350 try self.exprs.append(self.arena, start.expr);
1351 const end_index = self.exprs.items.len;
1352 try self.exprs.append(self.arena, end.expr);
1353 self.exprs.items[slice_index] = .{ .slice = .{ .lhs = lhs_index, .start = start_index, .end = end_index } };
1354
1355 const typeRef = switch (lhs.expr) {
1356 .declRef => |ref| self.decls.items[ref.Analyzed].value.typeRef,
1357 else => null,
1358 };
1359
1360 return DocData.WalkResult{
1361 .typeRef = typeRef,
1362 .expr = .{ .sliceIndex = slice_index },
1363 };
1364 },
1365 .slice_sentinel => {
1366 const pl_node = data[@intFromEnum(inst)].pl_node;
1367 const extra = file.zir.extraData(Zir.Inst.SliceSentinel, pl_node.payload_index);
1368
1369 const slice_index = self.exprs.items.len;
1370 try self.exprs.append(self.arena, .{ .slice = .{ .lhs = 0, .start = 0 } });
1371
1372 const lhs: DocData.WalkResult = try self.walkRef(
1373 file,
1374 parent_scope,
1375 parent_src,
1376 extra.data.lhs,
1377 false,
1378 call_ctx,
1379 );
1380 const start: DocData.WalkResult = try self.walkRef(
1381 file,
1382 parent_scope,
1383 parent_src,
1384 extra.data.start,
1385 false,
1386 call_ctx,
1387 );
1388 const end: DocData.WalkResult = try self.walkRef(
1389 file,
1390 parent_scope,
1391 parent_src,
1392 extra.data.end,
1393 false,
1394 call_ctx,
1395 );
1396 const sentinel: DocData.WalkResult = try self.walkRef(
1397 file,
1398 parent_scope,
1399 parent_src,
1400 extra.data.sentinel,
1401 false,
1402 call_ctx,
1403 );
1404
1405 const lhs_index = self.exprs.items.len;
1406 try self.exprs.append(self.arena, lhs.expr);
1407 const start_index = self.exprs.items.len;
1408 try self.exprs.append(self.arena, start.expr);
1409 const end_index = self.exprs.items.len;
1410 try self.exprs.append(self.arena, end.expr);
1411 const sentinel_index = self.exprs.items.len;
1412 try self.exprs.append(self.arena, sentinel.expr);
1413 self.exprs.items[slice_index] = .{ .slice = .{
1414 .lhs = lhs_index,
1415 .start = start_index,
1416 .end = end_index,
1417 .sentinel = sentinel_index,
1418 } };
1419
1420 const typeRef = switch (lhs.expr) {
1421 .declRef => |ref| self.decls.items[ref.Analyzed].value.typeRef,
1422 else => null,
1423 };
1424
1425 return DocData.WalkResult{
1426 .typeRef = typeRef,
1427 .expr = .{ .sliceIndex = slice_index },
1428 };
1429 },
1430 .slice_length => {
1431 const pl_node = data[@intFromEnum(inst)].pl_node;
1432 const extra = file.zir.extraData(Zir.Inst.SliceLength, pl_node.payload_index);
1433
1434 const slice_index = self.exprs.items.len;
1435 try self.exprs.append(self.arena, .{ .slice = .{ .lhs = 0, .start = 0 } });
1436
1437 const lhs: DocData.WalkResult = try self.walkRef(
1438 file,
1439 parent_scope,
1440 parent_src,
1441 extra.data.lhs,
1442 false,
1443 call_ctx,
1444 );
1445 const start: DocData.WalkResult = try self.walkRef(
1446 file,
1447 parent_scope,
1448 parent_src,
1449 extra.data.start,
1450 false,
1451 call_ctx,
1452 );
1453 const len: DocData.WalkResult = try self.walkRef(
1454 file,
1455 parent_scope,
1456 parent_src,
1457 extra.data.len,
1458 false,
1459 call_ctx,
1460 );
1461 const sentinel_opt: ?DocData.WalkResult = if (extra.data.sentinel != .none)
1462 try self.walkRef(
1463 file,
1464 parent_scope,
1465 parent_src,
1466 extra.data.sentinel,
1467 false,
1468 call_ctx,
1469 )
1470 else
1471 null;
1472
1473 const lhs_index = self.exprs.items.len;
1474 try self.exprs.append(self.arena, lhs.expr);
1475 const start_index = self.exprs.items.len;
1476 try self.exprs.append(self.arena, start.expr);
1477 const len_index = self.exprs.items.len;
1478 try self.exprs.append(self.arena, len.expr);
1479 const sentinel_index = if (sentinel_opt) |sentinel| sentinel_index: {
1480 const index = self.exprs.items.len;
1481 try self.exprs.append(self.arena, sentinel.expr);
1482 break :sentinel_index index;
1483 } else null;
1484 self.exprs.items[slice_index] = .{ .sliceLength = .{
1485 .lhs = lhs_index,
1486 .start = start_index,
1487 .len = len_index,
1488 .sentinel = sentinel_index,
1489 } };
1490
1491 const typeRef = switch (lhs.expr) {
1492 .declRef => |ref| self.decls.items[ref.Analyzed].value.typeRef,
1493 else => null,
1494 };
1495
1496 return DocData.WalkResult{
1497 .typeRef = typeRef,
1498 .expr = .{ .sliceIndex = slice_index },
1499 };
1500 },
1501
1502 .load => {
1503 const un_node = data[@intFromEnum(inst)].un_node;
1504 const operand = try self.walkRef(
1505 file,
1506 parent_scope,
1507 parent_src,
1508 un_node.operand,
1509 need_type,
1510 call_ctx,
1511 );
1512 const load_idx = self.exprs.items.len;
1513 try self.exprs.append(self.arena, operand.expr);
1514
1515 var typeRef: ?DocData.Expr = null;
1516 if (operand.typeRef) |ref| {
1517 switch (ref) {
1518 .type => |t_index| {
1519 switch (self.types.items[t_index]) {
1520 .Pointer => |p| typeRef = p.child,
1521 else => {},
1522 }
1523 },
1524 else => {},
1525 }
1526 }
1527
1528 return DocData.WalkResult{
1529 .typeRef = typeRef,
1530 .expr = .{ .load = load_idx },
1531 };
1532 },
1533 .ref => {
1534 const un_tok = data[@intFromEnum(inst)].un_tok;
1535 const operand = try self.walkRef(
1536 file,
1537 parent_scope,
1538 parent_src,
1539 un_tok.operand,
1540 need_type,
1541 call_ctx,
1542 );
1543 const ref_idx = self.exprs.items.len;
1544 try self.exprs.append(self.arena, operand.expr);
1545
1546 return DocData.WalkResult{
1547 .expr = .{ .@"&" = ref_idx },
1548 };
1549 },
1550
1551 .add,
1552 .addwrap,
1553 .add_sat,
1554 .sub,
1555 .subwrap,
1556 .sub_sat,
1557 .mul,
1558 .mulwrap,
1559 .mul_sat,
1560 .div,
1561 .shl,
1562 .shl_sat,
1563 .shr,
1564 .bit_or,
1565 .bit_and,
1566 .xor,
1567 .array_cat,
1568 => {
1569 const pl_node = data[@intFromEnum(inst)].pl_node;
1570 const extra = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index);
1571
1572 const binop_index = self.exprs.items.len;
1573 try self.exprs.append(self.arena, .{ .binOp = .{ .lhs = 0, .rhs = 0 } });
1574
1575 const lhs: DocData.WalkResult = try self.walkRef(
1576 file,
1577 parent_scope,
1578 parent_src,
1579 extra.data.lhs,
1580 false,
1581 call_ctx,
1582 );
1583 const rhs: DocData.WalkResult = try self.walkRef(
1584 file,
1585 parent_scope,
1586 parent_src,
1587 extra.data.rhs,
1588 false,
1589 call_ctx,
1590 );
1591
1592 const lhs_index = self.exprs.items.len;
1593 try self.exprs.append(self.arena, lhs.expr);
1594 const rhs_index = self.exprs.items.len;
1595 try self.exprs.append(self.arena, rhs.expr);
1596 self.exprs.items[binop_index] = .{ .binOp = .{
1597 .name = @tagName(tags[@intFromEnum(inst)]),
1598 .lhs = lhs_index,
1599 .rhs = rhs_index,
1600 } };
1601
1602 return DocData.WalkResult{
1603 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
1604 .expr = .{ .binOpIndex = binop_index },
1605 };
1606 },
1607 .array_mul => {
1608 const pl_node = data[@intFromEnum(inst)].pl_node;
1609 const extra = file.zir.extraData(Zir.Inst.ArrayMul, pl_node.payload_index);
1610
1611 const binop_index = self.exprs.items.len;
1612 try self.exprs.append(self.arena, .{ .binOp = .{ .lhs = 0, .rhs = 0 } });
1613
1614 const lhs: DocData.WalkResult = try self.walkRef(
1615 file,
1616 parent_scope,
1617 parent_src,
1618 extra.data.lhs,
1619 false,
1620 call_ctx,
1621 );
1622 const rhs: DocData.WalkResult = try self.walkRef(
1623 file,
1624 parent_scope,
1625 parent_src,
1626 extra.data.rhs,
1627 false,
1628 call_ctx,
1629 );
1630 const res_ty: ?DocData.WalkResult = if (extra.data.res_ty != .none)
1631 try self.walkRef(
1632 file,
1633 parent_scope,
1634 parent_src,
1635 extra.data.res_ty,
1636 false,
1637 call_ctx,
1638 )
1639 else
1640 null;
1641
1642 const lhs_index = self.exprs.items.len;
1643 try self.exprs.append(self.arena, lhs.expr);
1644 const rhs_index = self.exprs.items.len;
1645 try self.exprs.append(self.arena, rhs.expr);
1646 self.exprs.items[binop_index] = .{ .binOp = .{
1647 .name = @tagName(tags[@intFromEnum(inst)]),
1648 .lhs = lhs_index,
1649 .rhs = rhs_index,
1650 } };
1651
1652 return DocData.WalkResult{
1653 .typeRef = if (res_ty) |rt| rt.expr else null,
1654 .expr = .{ .binOpIndex = binop_index },
1655 };
1656 },
1657 // compare operators
1658 .cmp_eq,
1659 .cmp_neq,
1660 .cmp_gt,
1661 .cmp_gte,
1662 .cmp_lt,
1663 .cmp_lte,
1664 => {
1665 const pl_node = data[@intFromEnum(inst)].pl_node;
1666 const extra = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index);
1667
1668 const binop_index = self.exprs.items.len;
1669 try self.exprs.append(self.arena, .{ .binOp = .{ .lhs = 0, .rhs = 0 } });
1670
1671 const lhs: DocData.WalkResult = try self.walkRef(
1672 file,
1673 parent_scope,
1674 parent_src,
1675 extra.data.lhs,
1676 false,
1677 call_ctx,
1678 );
1679 const rhs: DocData.WalkResult = try self.walkRef(
1680 file,
1681 parent_scope,
1682 parent_src,
1683 extra.data.rhs,
1684 false,
1685 call_ctx,
1686 );
1687
1688 const lhs_index = self.exprs.items.len;
1689 try self.exprs.append(self.arena, lhs.expr);
1690 const rhs_index = self.exprs.items.len;
1691 try self.exprs.append(self.arena, rhs.expr);
1692 self.exprs.items[binop_index] = .{ .binOp = .{
1693 .name = @tagName(tags[@intFromEnum(inst)]),
1694 .lhs = lhs_index,
1695 .rhs = rhs_index,
1696 } };
1697
1698 return DocData.WalkResult{
1699 .typeRef = .{ .type = @intFromEnum(Ref.bool_type) },
1700 .expr = .{ .binOpIndex = binop_index },
1701 };
1702 },
1703
1704 // builtin functions
1705 .align_of,
1706 .int_from_bool,
1707 .embed_file,
1708 .error_name,
1709 .panic,
1710 .set_runtime_safety, // @check
1711 .sqrt,
1712 .sin,
1713 .cos,
1714 .tan,
1715 .exp,
1716 .exp2,
1717 .log,
1718 .log2,
1719 .log10,
1720 .abs,
1721 .floor,
1722 .ceil,
1723 .trunc,
1724 .round,
1725 .tag_name,
1726 .type_name,
1727 .frame_type,
1728 .frame_size,
1729 .int_from_ptr,
1730 .type_info,
1731 // @check
1732 .clz,
1733 .ctz,
1734 .pop_count,
1735 .byte_swap,
1736 .bit_reverse,
1737 => {
1738 const un_node = data[@intFromEnum(inst)].un_node;
1739 const bin_index = self.exprs.items.len;
1740 try self.exprs.append(self.arena, .{ .builtin = .{ .param = 0 } });
1741 const param = try self.walkRef(
1742 file,
1743 parent_scope,
1744 parent_src,
1745 un_node.operand,
1746 false,
1747 call_ctx,
1748 );
1749
1750 const param_index = self.exprs.items.len;
1751 try self.exprs.append(self.arena, param.expr);
1752
1753 self.exprs.items[bin_index] = .{
1754 .builtin = .{
1755 .name = @tagName(tags[@intFromEnum(inst)]),
1756 .param = param_index,
1757 },
1758 };
1759
1760 return DocData.WalkResult{
1761 .typeRef = param.typeRef orelse .{ .type = @intFromEnum(Ref.type_type) },
1762 .expr = .{ .builtinIndex = bin_index },
1763 };
1764 },
1765 .bit_not,
1766 .bool_not,
1767 .negate_wrap,
1768 => {
1769 const un_node = data[@intFromEnum(inst)].un_node;
1770 const un_index = self.exprs.items.len;
1771 try self.exprs.append(self.arena, .{ .unOp = .{ .param = 0 } });
1772 const param = try self.walkRef(
1773 file,
1774 parent_scope,
1775 parent_src,
1776 un_node.operand,
1777 false,
1778 call_ctx,
1779 );
1780
1781 const param_index = self.exprs.items.len;
1782 try self.exprs.append(self.arena, param.expr);
1783
1784 self.exprs.items[un_index] = .{
1785 .unOp = .{
1786 .name = @tagName(tags[@intFromEnum(inst)]),
1787 .param = param_index,
1788 },
1789 };
1790
1791 return DocData.WalkResult{
1792 .typeRef = param.typeRef,
1793 .expr = .{ .unOpIndex = un_index },
1794 };
1795 },
1796 .bool_br_and, .bool_br_or => {
1797 const pl_node = data[@intFromEnum(inst)].pl_node;
1798 const extra = file.zir.extraData(Zir.Inst.BoolBr, pl_node.payload_index);
1799
1800 const bin_index = self.exprs.items.len;
1801 try self.exprs.append(self.arena, .{ .binOp = .{ .lhs = 0, .rhs = 0 } });
1802
1803 const lhs = try self.walkRef(
1804 file,
1805 parent_scope,
1806 parent_src,
1807 extra.data.lhs,
1808 false,
1809 call_ctx,
1810 );
1811 const lhs_index = self.exprs.items.len;
1812 try self.exprs.append(self.arena, lhs.expr);
1813
1814 const rhs = try self.walkInstruction(
1815 file,
1816 parent_scope,
1817 parent_src,
1818 @enumFromInt(file.zir.extra[extra.end..][extra.data.body_len - 1]),
1819 false,
1820 call_ctx,
1821 );
1822 const rhs_index = self.exprs.items.len;
1823 try self.exprs.append(self.arena, rhs.expr);
1824
1825 self.exprs.items[bin_index] = .{ .binOp = .{ .name = @tagName(tags[@intFromEnum(inst)]), .lhs = lhs_index, .rhs = rhs_index } };
1826
1827 return DocData.WalkResult{
1828 .typeRef = .{ .type = @intFromEnum(Ref.bool_type) },
1829 .expr = .{ .binOpIndex = bin_index },
1830 };
1831 },
1832 .truncate => {
1833 // in the ZIR this node is a builtin `bin` but we want send it as a `un` builtin
1834 const pl_node = data[@intFromEnum(inst)].pl_node;
1835 const extra = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index);
1836
1837 const rhs: DocData.WalkResult = try self.walkRef(
1838 file,
1839 parent_scope,
1840 parent_src,
1841 extra.data.rhs,
1842 false,
1843 call_ctx,
1844 );
1845
1846 const bin_index = self.exprs.items.len;
1847 try self.exprs.append(self.arena, .{ .builtin = .{ .param = 0 } });
1848
1849 const rhs_index = self.exprs.items.len;
1850 try self.exprs.append(self.arena, rhs.expr);
1851
1852 const lhs: DocData.WalkResult = try self.walkRef(
1853 file,
1854 parent_scope,
1855 parent_src,
1856 extra.data.lhs,
1857 false,
1858 call_ctx,
1859 );
1860
1861 self.exprs.items[bin_index] = .{ .builtin = .{ .name = @tagName(tags[@intFromEnum(inst)]), .param = rhs_index } };
1862
1863 return DocData.WalkResult{
1864 .typeRef = lhs.expr,
1865 .expr = .{ .builtinIndex = bin_index },
1866 };
1867 },
1868 .int_from_float,
1869 .float_from_int,
1870 .ptr_from_int,
1871 .enum_from_int,
1872 .float_cast,
1873 .int_cast,
1874 .ptr_cast,
1875 .has_decl,
1876 .has_field,
1877 .div_exact,
1878 .div_floor,
1879 .div_trunc,
1880 .mod,
1881 .rem,
1882 .mod_rem,
1883 .shl_exact,
1884 .shr_exact,
1885 .bitcast,
1886 .vector_type,
1887 // @check
1888 .bit_offset_of,
1889 .offset_of,
1890 .splat,
1891 .reduce,
1892 .min,
1893 .max,
1894 => {
1895 const pl_node = data[@intFromEnum(inst)].pl_node;
1896 const extra = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index);
1897
1898 const binop_index = self.exprs.items.len;
1899 try self.exprs.append(self.arena, .{ .builtinBin = .{ .lhs = 0, .rhs = 0 } });
1900
1901 const lhs: DocData.WalkResult = try self.walkRef(
1902 file,
1903 parent_scope,
1904 parent_src,
1905 extra.data.lhs,
1906 false,
1907 call_ctx,
1908 );
1909 const rhs: DocData.WalkResult = try self.walkRef(
1910 file,
1911 parent_scope,
1912 parent_src,
1913 extra.data.rhs,
1914 false,
1915 call_ctx,
1916 );
1917
1918 const lhs_index = self.exprs.items.len;
1919 try self.exprs.append(self.arena, lhs.expr);
1920 const rhs_index = self.exprs.items.len;
1921 try self.exprs.append(self.arena, rhs.expr);
1922 self.exprs.items[binop_index] = .{ .builtinBin = .{ .name = @tagName(tags[@intFromEnum(inst)]), .lhs = lhs_index, .rhs = rhs_index } };
1923
1924 return DocData.WalkResult{
1925 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
1926 .expr = .{ .builtinBinIndex = binop_index },
1927 };
1928 },
1929 .mul_add => {
1930 const pl_node = data[@intFromEnum(inst)].pl_node;
1931 const extra = file.zir.extraData(Zir.Inst.MulAdd, pl_node.payload_index);
1932
1933 const mul1: DocData.WalkResult = try self.walkRef(
1934 file,
1935 parent_scope,
1936 parent_src,
1937 extra.data.mulend1,
1938 false,
1939 call_ctx,
1940 );
1941 const mul2: DocData.WalkResult = try self.walkRef(
1942 file,
1943 parent_scope,
1944 parent_src,
1945 extra.data.mulend2,
1946 false,
1947 call_ctx,
1948 );
1949 const add: DocData.WalkResult = try self.walkRef(
1950 file,
1951 parent_scope,
1952 parent_src,
1953 extra.data.addend,
1954 false,
1955 call_ctx,
1956 );
1957
1958 const mul1_index = self.exprs.items.len;
1959 try self.exprs.append(self.arena, mul1.expr);
1960 const mul2_index = self.exprs.items.len;
1961 try self.exprs.append(self.arena, mul2.expr);
1962 const add_index = self.exprs.items.len;
1963 try self.exprs.append(self.arena, add.expr);
1964
1965 const type_index: usize = self.exprs.items.len;
1966 try self.exprs.append(self.arena, add.typeRef orelse .{ .type = @intFromEnum(Ref.type_type) });
1967
1968 return DocData.WalkResult{
1969 .typeRef = add.typeRef,
1970 .expr = .{
1971 .mulAdd = .{
1972 .mulend1 = mul1_index,
1973 .mulend2 = mul2_index,
1974 .addend = add_index,
1975 .type = type_index,
1976 },
1977 },
1978 };
1979 },
1980 .union_init => {
1981 const pl_node = data[@intFromEnum(inst)].pl_node;
1982 const extra = file.zir.extraData(Zir.Inst.UnionInit, pl_node.payload_index);
1983
1984 const union_type: DocData.WalkResult = try self.walkRef(
1985 file,
1986 parent_scope,
1987 parent_src,
1988 extra.data.union_type,
1989 false,
1990 call_ctx,
1991 );
1992 const field_name: DocData.WalkResult = try self.walkRef(
1993 file,
1994 parent_scope,
1995 parent_src,
1996 extra.data.field_name,
1997 false,
1998 call_ctx,
1999 );
2000 const init: DocData.WalkResult = try self.walkRef(
2001 file,
2002 parent_scope,
2003 parent_src,
2004 extra.data.init,
2005 false,
2006 call_ctx,
2007 );
2008
2009 const union_type_index = self.exprs.items.len;
2010 try self.exprs.append(self.arena, union_type.expr);
2011 const field_name_index = self.exprs.items.len;
2012 try self.exprs.append(self.arena, field_name.expr);
2013 const init_index = self.exprs.items.len;
2014 try self.exprs.append(self.arena, init.expr);
2015
2016 return DocData.WalkResult{
2017 .typeRef = union_type.expr,
2018 .expr = .{
2019 .unionInit = .{
2020 .type = union_type_index,
2021 .field = field_name_index,
2022 .init = init_index,
2023 },
2024 },
2025 };
2026 },
2027 .builtin_call => {
2028 const pl_node = data[@intFromEnum(inst)].pl_node;
2029 const extra = file.zir.extraData(Zir.Inst.BuiltinCall, pl_node.payload_index);
2030
2031 const modifier: DocData.WalkResult = try self.walkRef(
2032 file,
2033 parent_scope,
2034 parent_src,
2035 extra.data.modifier,
2036 false,
2037 call_ctx,
2038 );
2039
2040 const callee: DocData.WalkResult = try self.walkRef(
2041 file,
2042 parent_scope,
2043 parent_src,
2044 extra.data.callee,
2045 false,
2046 call_ctx,
2047 );
2048
2049 const args: DocData.WalkResult = try self.walkRef(
2050 file,
2051 parent_scope,
2052 parent_src,
2053 extra.data.args,
2054 false,
2055 call_ctx,
2056 );
2057
2058 const modifier_index = self.exprs.items.len;
2059 try self.exprs.append(self.arena, modifier.expr);
2060 const function_index = self.exprs.items.len;
2061 try self.exprs.append(self.arena, callee.expr);
2062 const args_index = self.exprs.items.len;
2063 try self.exprs.append(self.arena, args.expr);
2064
2065 return DocData.WalkResult{
2066 .expr = .{
2067 .builtinCall = .{
2068 .modifier = modifier_index,
2069 .function = function_index,
2070 .args = args_index,
2071 },
2072 },
2073 };
2074 },
2075 .error_union_type => {
2076 const pl_node = data[@intFromEnum(inst)].pl_node;
2077 const extra = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index);
2078
2079 const lhs: DocData.WalkResult = try self.walkRef(
2080 file,
2081 parent_scope,
2082 parent_src,
2083 extra.data.lhs,
2084 false,
2085 call_ctx,
2086 );
2087 const rhs: DocData.WalkResult = try self.walkRef(
2088 file,
2089 parent_scope,
2090 parent_src,
2091 extra.data.rhs,
2092 false,
2093 call_ctx,
2094 );
2095
2096 const type_slot_index = self.types.items.len;
2097 try self.types.append(self.arena, .{ .ErrorUnion = .{
2098 .lhs = lhs.expr,
2099 .rhs = rhs.expr,
2100 } });
2101
2102 return DocData.WalkResult{
2103 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
2104 .expr = .{ .errorUnion = type_slot_index },
2105 };
2106 },
2107 .merge_error_sets => {
2108 const pl_node = data[@intFromEnum(inst)].pl_node;
2109 const extra = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index);
2110
2111 const lhs: DocData.WalkResult = try self.walkRef(
2112 file,
2113 parent_scope,
2114 parent_src,
2115 extra.data.lhs,
2116 false,
2117 call_ctx,
2118 );
2119 const rhs: DocData.WalkResult = try self.walkRef(
2120 file,
2121 parent_scope,
2122 parent_src,
2123 extra.data.rhs,
2124 false,
2125 call_ctx,
2126 );
2127 const type_slot_index = self.types.items.len;
2128 try self.types.append(self.arena, .{ .ErrorUnion = .{
2129 .lhs = lhs.expr,
2130 .rhs = rhs.expr,
2131 } });
2132
2133 return DocData.WalkResult{
2134 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
2135 .expr = .{ .errorSets = type_slot_index },
2136 };
2137 },
2138 // .elem_type => {
2139 // const un_node = data[@intFromEnum(inst)].un_node;
2140
2141 // const operand: DocData.WalkResult = try self.walkRef(
2142 // file,
2143 // parent_scope, parent_src,
2144 // un_node.operand,
2145 // false,
2146 // );
2147
2148 // return operand;
2149 // },
2150 .ptr_type => {
2151 const ptr = data[@intFromEnum(inst)].ptr_type;
2152 const extra = file.zir.extraData(Zir.Inst.PtrType, ptr.payload_index);
2153 var extra_index = extra.end;
2154
2155 const elem_type_ref = try self.walkRef(
2156 file,
2157 parent_scope,
2158 parent_src,
2159 extra.data.elem_type,
2160 false,
2161 call_ctx,
2162 );
2163
2164 // @check if `addrspace`, `bit_start` and `host_size` really need to be
2165 // present in json
2166 var sentinel: ?DocData.Expr = null;
2167 if (ptr.flags.has_sentinel) {
2168 const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
2169 const ref_result = try self.walkRef(
2170 file,
2171 parent_scope,
2172 parent_src,
2173 ref,
2174 false,
2175 call_ctx,
2176 );
2177 sentinel = ref_result.expr;
2178 extra_index += 1;
2179 }
2180
2181 var @"align": ?DocData.Expr = null;
2182 if (ptr.flags.has_align) {
2183 const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
2184 const ref_result = try self.walkRef(
2185 file,
2186 parent_scope,
2187 parent_src,
2188 ref,
2189 false,
2190 call_ctx,
2191 );
2192 @"align" = ref_result.expr;
2193 extra_index += 1;
2194 }
2195 var address_space: ?DocData.Expr = null;
2196 if (ptr.flags.has_addrspace) {
2197 const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
2198 const ref_result = try self.walkRef(
2199 file,
2200 parent_scope,
2201 parent_src,
2202 ref,
2203 false,
2204 call_ctx,
2205 );
2206 address_space = ref_result.expr;
2207 extra_index += 1;
2208 }
2209 const bit_start: ?DocData.Expr = null;
2210 if (ptr.flags.has_bit_range) {
2211 const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
2212 const ref_result = try self.walkRef(
2213 file,
2214 parent_scope,
2215 parent_src,
2216 ref,
2217 false,
2218 call_ctx,
2219 );
2220 address_space = ref_result.expr;
2221 extra_index += 1;
2222 }
2223
2224 var host_size: ?DocData.Expr = null;
2225 if (ptr.flags.has_bit_range) {
2226 const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
2227 const ref_result = try self.walkRef(
2228 file,
2229 parent_scope,
2230 parent_src,
2231 ref,
2232 false,
2233 call_ctx,
2234 );
2235 host_size = ref_result.expr;
2236 }
2237
2238 const type_slot_index = self.types.items.len;
2239 try self.types.append(self.arena, .{
2240 .Pointer = .{
2241 .size = ptr.size,
2242 .child = elem_type_ref.expr,
2243 .has_align = ptr.flags.has_align,
2244 .@"align" = @"align",
2245 .has_addrspace = ptr.flags.has_addrspace,
2246 .address_space = address_space,
2247 .has_sentinel = ptr.flags.has_sentinel,
2248 .sentinel = sentinel,
2249 .is_mutable = ptr.flags.is_mutable,
2250 .is_volatile = ptr.flags.is_volatile,
2251 .has_bit_range = ptr.flags.has_bit_range,
2252 .bit_start = bit_start,
2253 .host_size = host_size,
2254 },
2255 });
2256 return DocData.WalkResult{
2257 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
2258 .expr = .{ .type = type_slot_index },
2259 };
2260 },
2261 .array_type => {
2262 const pl_node = data[@intFromEnum(inst)].pl_node;
2263
2264 const bin = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index).data;
2265 const len = try self.walkRef(
2266 file,
2267 parent_scope,
2268 parent_src,
2269 bin.lhs,
2270 false,
2271 call_ctx,
2272 );
2273 const child = try self.walkRef(
2274 file,
2275 parent_scope,
2276 parent_src,
2277 bin.rhs,
2278 false,
2279 call_ctx,
2280 );
2281
2282 const type_slot_index = self.types.items.len;
2283 try self.types.append(self.arena, .{
2284 .Array = .{
2285 .len = len.expr,
2286 .child = child.expr,
2287 },
2288 });
2289
2290 return DocData.WalkResult{
2291 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
2292 .expr = .{ .type = type_slot_index },
2293 };
2294 },
2295 .array_type_sentinel => {
2296 const pl_node = data[@intFromEnum(inst)].pl_node;
2297 const extra = file.zir.extraData(Zir.Inst.ArrayTypeSentinel, pl_node.payload_index);
2298 const len = try self.walkRef(
2299 file,
2300 parent_scope,
2301 parent_src,
2302 extra.data.len,
2303 false,
2304 call_ctx,
2305 );
2306 const sentinel = try self.walkRef(
2307 file,
2308 parent_scope,
2309 parent_src,
2310 extra.data.sentinel,
2311 false,
2312 call_ctx,
2313 );
2314 const elem_type = try self.walkRef(
2315 file,
2316 parent_scope,
2317 parent_src,
2318 extra.data.elem_type,
2319 false,
2320 call_ctx,
2321 );
2322
2323 const type_slot_index = self.types.items.len;
2324 try self.types.append(self.arena, .{
2325 .Array = .{
2326 .len = len.expr,
2327 .child = elem_type.expr,
2328 .sentinel = sentinel.expr,
2329 },
2330 });
2331 return DocData.WalkResult{
2332 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
2333 .expr = .{ .type = type_slot_index },
2334 };
2335 },
2336 .array_init => {
2337 const pl_node = data[@intFromEnum(inst)].pl_node;
2338 const extra = file.zir.extraData(Zir.Inst.MultiOp, pl_node.payload_index);
2339 const operands = file.zir.refSlice(extra.end, extra.data.operands_len);
2340 const array_data = try self.arena.alloc(usize, operands.len - 1);
2341
2342 std.debug.assert(operands.len > 0);
2343 const array_type = try self.walkRef(
2344 file,
2345 parent_scope,
2346 parent_src,
2347 operands[0],
2348 false,
2349 call_ctx,
2350 );
2351
2352 for (operands[1..], 0..) |op, idx| {
2353 const wr = try self.walkRef(
2354 file,
2355 parent_scope,
2356 parent_src,
2357 op,
2358 false,
2359 call_ctx,
2360 );
2361 const expr_index = self.exprs.items.len;
2362 try self.exprs.append(self.arena, wr.expr);
2363 array_data[idx] = expr_index;
2364 }
2365
2366 return DocData.WalkResult{
2367 .typeRef = array_type.expr,
2368 .expr = .{ .array = array_data },
2369 };
2370 },
2371 .array_init_anon => {
2372 const pl_node = data[@intFromEnum(inst)].pl_node;
2373 const extra = file.zir.extraData(Zir.Inst.MultiOp, pl_node.payload_index);
2374 const operands = file.zir.refSlice(extra.end, extra.data.operands_len);
2375 const array_data = try self.arena.alloc(usize, operands.len);
2376
2377 for (operands, 0..) |op, idx| {
2378 const wr = try self.walkRef(
2379 file,
2380 parent_scope,
2381 parent_src,
2382 op,
2383 false,
2384 call_ctx,
2385 );
2386 const expr_index = self.exprs.items.len;
2387 try self.exprs.append(self.arena, wr.expr);
2388 array_data[idx] = expr_index;
2389 }
2390
2391 return DocData.WalkResult{
2392 .typeRef = null,
2393 .expr = .{ .array = array_data },
2394 };
2395 },
2396 .array_init_ref => {
2397 const pl_node = data[@intFromEnum(inst)].pl_node;
2398 const extra = file.zir.extraData(Zir.Inst.MultiOp, pl_node.payload_index);
2399 const operands = file.zir.refSlice(extra.end, extra.data.operands_len);
2400 const array_data = try self.arena.alloc(usize, operands.len - 1);
2401
2402 std.debug.assert(operands.len > 0);
2403 const array_type = try self.walkRef(
2404 file,
2405 parent_scope,
2406 parent_src,
2407 operands[0],
2408 false,
2409 call_ctx,
2410 );
2411
2412 for (operands[1..], 0..) |op, idx| {
2413 const wr = try self.walkRef(
2414 file,
2415 parent_scope,
2416 parent_src,
2417 op,
2418 false,
2419 call_ctx,
2420 );
2421 const expr_index = self.exprs.items.len;
2422 try self.exprs.append(self.arena, wr.expr);
2423 array_data[idx] = expr_index;
2424 }
2425
2426 const type_slot_index = self.types.items.len;
2427 try self.types.append(self.arena, .{
2428 .Pointer = .{
2429 .size = .One,
2430 .child = array_type.expr,
2431 },
2432 });
2433
2434 const expr_index = self.exprs.items.len;
2435 try self.exprs.append(self.arena, .{ .array = array_data });
2436
2437 return DocData.WalkResult{
2438 .typeRef = .{ .type = type_slot_index },
2439 .expr = .{ .@"&" = expr_index },
2440 };
2441 },
2442 .float => {
2443 const float = data[@intFromEnum(inst)].float;
2444 return DocData.WalkResult{
2445 .typeRef = .{ .type = @intFromEnum(Ref.comptime_float_type) },
2446 .expr = .{ .float = float },
2447 };
2448 },
2449 // @check: In frontend I'm handling float128 with `.toFixed(2)`
2450 .float128 => {
2451 const pl_node = data[@intFromEnum(inst)].pl_node;
2452 const extra = file.zir.extraData(Zir.Inst.Float128, pl_node.payload_index);
2453 return DocData.WalkResult{
2454 .typeRef = .{ .type = @intFromEnum(Ref.comptime_float_type) },
2455 .expr = .{ .float128 = extra.data.get() },
2456 };
2457 },
2458 .negate => {
2459 const un_node = data[@intFromEnum(inst)].un_node;
2460
2461 var operand: DocData.WalkResult = try self.walkRef(
2462 file,
2463 parent_scope,
2464 parent_src,
2465 un_node.operand,
2466 need_type,
2467 call_ctx,
2468 );
2469 switch (operand.expr) {
2470 .int => |*int| int.negated = true,
2471 .int_big => |*int_big| int_big.negated = true,
2472 else => {
2473 const un_index = self.exprs.items.len;
2474 try self.exprs.append(self.arena, .{ .unOp = .{ .param = 0 } });
2475 const param_index = self.exprs.items.len;
2476 try self.exprs.append(self.arena, operand.expr);
2477 self.exprs.items[un_index] = .{
2478 .unOp = .{
2479 .name = @tagName(tags[@intFromEnum(inst)]),
2480 .param = param_index,
2481 },
2482 };
2483 return DocData.WalkResult{
2484 .typeRef = operand.typeRef,
2485 .expr = .{ .unOpIndex = un_index },
2486 };
2487 },
2488 }
2489 return operand;
2490 },
2491 .size_of => {
2492 const un_node = data[@intFromEnum(inst)].un_node;
2493
2494 const operand = try self.walkRef(
2495 file,
2496 parent_scope,
2497 parent_src,
2498 un_node.operand,
2499 false,
2500 call_ctx,
2501 );
2502 const operand_index = self.exprs.items.len;
2503 try self.exprs.append(self.arena, operand.expr);
2504 return DocData.WalkResult{
2505 .typeRef = .{ .type = @intFromEnum(Ref.comptime_int_type) },
2506 .expr = .{ .sizeOf = operand_index },
2507 };
2508 },
2509 .bit_size_of => {
2510 // not working correctly with `align()`
2511 const un_node = data[@intFromEnum(inst)].un_node;
2512
2513 const operand = try self.walkRef(
2514 file,
2515 parent_scope,
2516 parent_src,
2517 un_node.operand,
2518 need_type,
2519 call_ctx,
2520 );
2521 const operand_index = self.exprs.items.len;
2522 try self.exprs.append(self.arena, operand.expr);
2523
2524 return DocData.WalkResult{
2525 .typeRef = operand.typeRef,
2526 .expr = .{ .bitSizeOf = operand_index },
2527 };
2528 },
2529 .int_from_enum => {
2530 // not working correctly with `align()`
2531 const un_node = data[@intFromEnum(inst)].un_node;
2532 const operand = try self.walkRef(
2533 file,
2534 parent_scope,
2535 parent_src,
2536 un_node.operand,
2537 false,
2538 call_ctx,
2539 );
2540 const builtin_index = self.exprs.items.len;
2541 try self.exprs.append(self.arena, .{ .builtin = .{ .param = 0 } });
2542 const operand_index = self.exprs.items.len;
2543 try self.exprs.append(self.arena, operand.expr);
2544 self.exprs.items[builtin_index] = .{
2545 .builtin = .{
2546 .name = @tagName(tags[@intFromEnum(inst)]),
2547 .param = operand_index,
2548 },
2549 };
2550
2551 return DocData.WalkResult{
2552 .typeRef = .{ .type = @intFromEnum(Ref.comptime_int_type) },
2553 .expr = .{ .builtinIndex = builtin_index },
2554 };
2555 },
2556 .switch_block => {
2557 // WIP
2558 const pl_node = data[@intFromEnum(inst)].pl_node;
2559 const extra = file.zir.extraData(Zir.Inst.SwitchBlock, pl_node.payload_index);
2560
2561 const switch_cond = try self.walkRef(
2562 file,
2563 parent_scope,
2564 parent_src,
2565 extra.data.operand,
2566 false,
2567 call_ctx,
2568 );
2569 const cond_index = self.exprs.items.len;
2570 try self.exprs.append(self.arena, switch_cond.expr);
2571 _ = cond_index;
2572
2573 // const ast_index = self.ast_nodes.items.len;
2574 // const type_index = self.types.items.len - 1;
2575
2576 // const ast_line = self.ast_nodes.items[ast_index - 1];
2577
2578 // const sep = "=" ** 200;
2579 // log.debug("{s}", .{sep});
2580 // log.debug("SWITCH BLOCK", .{});
2581 // log.debug("extra = {any}", .{extra});
2582 // log.debug("outer_decl = {any}", .{self.types.items[type_index]});
2583 // log.debug("ast_lines = {}", .{ast_line});
2584 // log.debug("{s}", .{sep});
2585
2586 const switch_index = self.exprs.items.len;
2587
2588 // const src_loc = try self.srcLocInfo(file, pl_node.src_node, parent_src);
2589
2590 const switch_expr = try self.getBlockSource(file, parent_src, pl_node.src_node);
2591 try self.exprs.append(self.arena, .{ .comptimeExpr = self.comptime_exprs.items.len });
2592 try self.comptime_exprs.append(self.arena, .{ .code = switch_expr });
2593 // try self.exprs.append(self.arena, .{ .switchOp = .{
2594 // .cond_index = cond_index,
2595 // .file_name = file.sub_file_path,
2596 // .src = ast_index,
2597 // .outer_decl = type_index,
2598 // } });
2599
2600 return DocData.WalkResult{
2601 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
2602 .expr = .{ .switchIndex = switch_index },
2603 };
2604 },
2605
2606 .typeof => {
2607 const un_node = data[@intFromEnum(inst)].un_node;
2608
2609 const operand = try self.walkRef(
2610 file,
2611 parent_scope,
2612 parent_src,
2613 un_node.operand,
2614 need_type,
2615 call_ctx,
2616 );
2617 const operand_index = self.exprs.items.len;
2618 try self.exprs.append(self.arena, operand.expr);
2619
2620 return DocData.WalkResult{
2621 .typeRef = operand.typeRef,
2622 .expr = .{ .typeOf = operand_index },
2623 };
2624 },
2625 .typeof_builtin => {
2626 const pl_node = data[@intFromEnum(inst)].pl_node;
2627 const extra = file.zir.extraData(Zir.Inst.Block, pl_node.payload_index);
2628 const body = file.zir.extra[extra.end..][extra.data.body_len - 1];
2629 const operand: DocData.WalkResult = try self.walkRef(
2630 file,
2631 parent_scope,
2632 parent_src,
2633 data[body].@"break".operand,
2634 false,
2635 call_ctx,
2636 );
2637
2638 const operand_index = self.exprs.items.len;
2639 try self.exprs.append(self.arena, operand.expr);
2640
2641 return DocData.WalkResult{
2642 .typeRef = operand.typeRef,
2643 .expr = .{ .typeOf = operand_index },
2644 };
2645 },
2646 .as_node, .as_shift_operand => {
2647 const pl_node = data[@intFromEnum(inst)].pl_node;
2648 const extra = file.zir.extraData(Zir.Inst.As, pl_node.payload_index);
2649
2650 // Skip the as_node if the destination type is a call instruction
2651 if (extra.data.dest_type.toIndex()) |dti| {
2652 var maybe_cc = call_ctx;
2653 while (maybe_cc) |cc| : (maybe_cc = cc.prev) {
2654 if (cc.inst == dti) {
2655 return try self.walkRef(
2656 file,
2657 parent_scope,
2658 parent_src,
2659 extra.data.operand,
2660 false,
2661 call_ctx,
2662 );
2663 }
2664 }
2665 }
2666
2667 const dest_type_walk = try self.walkRef(
2668 file,
2669 parent_scope,
2670 parent_src,
2671 extra.data.dest_type,
2672 false,
2673 call_ctx,
2674 );
2675
2676 const operand = try self.walkRef(
2677 file,
2678 parent_scope,
2679 parent_src,
2680 extra.data.operand,
2681 false,
2682 call_ctx,
2683 );
2684
2685 const operand_idx = self.exprs.items.len;
2686 try self.exprs.append(self.arena, operand.expr);
2687
2688 const dest_type_idx = self.exprs.items.len;
2689 try self.exprs.append(self.arena, dest_type_walk.expr);
2690
2691 // TODO: there's something wrong with how both `as` and `WalkrResult`
2692 // try to store type information.
2693 return DocData.WalkResult{
2694 .typeRef = dest_type_walk.expr,
2695 .expr = .{
2696 .as = .{
2697 .typeRefArg = dest_type_idx,
2698 .exprArg = operand_idx,
2699 },
2700 },
2701 };
2702 },
2703 .optional_type => {
2704 const un_node = data[@intFromEnum(inst)].un_node;
2705
2706 const operand: DocData.WalkResult = try self.walkRef(
2707 file,
2708 parent_scope,
2709 parent_src,
2710 un_node.operand,
2711 false,
2712 call_ctx,
2713 );
2714
2715 const operand_idx = self.types.items.len;
2716 try self.types.append(self.arena, .{
2717 .Optional = .{ .name = "?TODO", .child = operand.expr },
2718 });
2719
2720 return DocData.WalkResult{
2721 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
2722 .expr = .{ .type = operand_idx },
2723 };
2724 },
2725 .decl_val, .decl_ref => {
2726 const str_tok = data[@intFromEnum(inst)].str_tok;
2727 const decl_status = parent_scope.resolveDeclName(str_tok.start, file, inst.toOptional());
2728 return DocData.WalkResult{
2729 .expr = .{ .declRef = decl_status },
2730 };
2731 },
2732 .field_val, .field_ptr => {
2733 const pl_node = data[@intFromEnum(inst)].pl_node;
2734 const extra = file.zir.extraData(Zir.Inst.Field, pl_node.payload_index);
2735
2736 var path: std.ArrayListUnmanaged(DocData.Expr) = .{};
2737 try path.append(self.arena, .{
2738 .declName = file.zir.nullTerminatedString(extra.data.field_name_start),
2739 });
2740
2741 // Put inside path the starting index of each decl name that
2742 // we encounter as we navigate through all the field_*s
2743 const lhs_ref = blk: {
2744 var lhs_extra = extra;
2745 while (true) {
2746 const lhs = @intFromEnum(lhs_extra.data.lhs.toIndex() orelse {
2747 break :blk lhs_extra.data.lhs;
2748 });
2749
2750 if (tags[lhs] != .field_val and
2751 tags[lhs] != .field_ptr)
2752 {
2753 break :blk lhs_extra.data.lhs;
2754 }
2755
2756 lhs_extra = file.zir.extraData(
2757 Zir.Inst.Field,
2758 data[lhs].pl_node.payload_index,
2759 );
2760
2761 try path.append(self.arena, .{
2762 .declName = file.zir.nullTerminatedString(lhs_extra.data.field_name_start),
2763 });
2764 }
2765 };
2766
2767 // If the lhs is a `call` instruction, it means that we're inside
2768 // a function call and we're referring to one of its arguments.
2769 // We can't just blindly analyze the instruction or we will
2770 // start recursing forever.
2771 // TODO: add proper resolution of the container type for `calls`
2772 // TODO: we're like testing lhs as an instruction twice
2773 // (above and below) this todo, maybe a cleaer solution woul
2774 // avoid that.
2775 // TODO: double check that we really don't need type info here
2776
2777 const wr = blk: {
2778 if (lhs_ref.toIndex()) |lhs_inst| switch (tags[@intFromEnum(lhs_inst)]) {
2779 .call, .field_call => {
2780 break :blk DocData.WalkResult{
2781 .expr = .{
2782 .comptimeExpr = 0,
2783 },
2784 };
2785 },
2786 else => {},
2787 };
2788
2789 break :blk try self.walkRef(
2790 file,
2791 parent_scope,
2792 parent_src,
2793 lhs_ref,
2794 false,
2795 call_ctx,
2796 );
2797 };
2798 try path.append(self.arena, wr.expr);
2799
2800 // This way the data in `path` has the same ordering that the ref
2801 // path has in the text: most general component first.
2802 std.mem.reverse(DocData.Expr, path.items);
2803
2804 // Righ now, every element of `path` is a string except its first
2805 // element (at index 0). We're now going to attempt to resolve each
2806 // string. If one or more components in this path are not yet fully
2807 // analyzed, the path will only be solved partially, but we expect
2808 // to eventually solve it fully(or give up in case of a
2809 // comptimeExpr). This means that:
2810 // - (1) Paths can be not fully analyzed temporarily, so any code
2811 // that requires to know where a ref path leads to, neeeds to
2812 // implement support for lazyness (see self.pending_ref_paths)
2813 // - (2) Paths can sometimes never resolve fully. This means that
2814 // any value that depends on that will have to become a
2815 // comptimeExpr.
2816 try self.tryResolveRefPath(file, inst, path.items);
2817 return DocData.WalkResult{ .expr = .{ .refPath = path.items } };
2818 },
2819 .int_type => {
2820 const int_type = data[@intFromEnum(inst)].int_type;
2821 const sign = if (int_type.signedness == .unsigned) "u" else "i";
2822 const bits = int_type.bit_count;
2823 const name = try std.fmt.allocPrint(self.arena, "{s}{}", .{ sign, bits });
2824
2825 try self.types.append(self.arena, .{
2826 .Int = .{ .name = name },
2827 });
2828
2829 return DocData.WalkResult{
2830 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
2831 .expr = .{ .type = self.types.items.len - 1 },
2832 };
2833 },
2834 .block => {
2835 const res = DocData.WalkResult{
2836 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
2837 .expr = .{ .comptimeExpr = self.comptime_exprs.items.len },
2838 };
2839 const pl_node = data[@intFromEnum(inst)].pl_node;
2840 const block_expr = try self.getBlockSource(file, parent_src, pl_node.src_node);
2841 try self.comptime_exprs.append(self.arena, .{
2842 .code = block_expr,
2843 });
2844 return res;
2845 },
2846 .block_inline => {
2847 const pl_node = data[@intFromEnum(inst)].pl_node;
2848 const extra = file.zir.extraData(Zir.Inst.Block, pl_node.payload_index);
2849 return self.walkInlineBody(
2850 file,
2851 parent_scope,
2852 try self.srcLocInfo(file, pl_node.src_node, parent_src),
2853 parent_src,
2854 file.zir.bodySlice(extra.end, extra.data.body_len),
2855 need_type,
2856 call_ctx,
2857 );
2858 },
2859 .break_inline => {
2860 const @"break" = data[@intFromEnum(inst)].@"break";
2861 return try self.walkRef(
2862 file,
2863 parent_scope,
2864 parent_src,
2865 @"break".operand,
2866 need_type,
2867 call_ctx,
2868 );
2869 },
2870 .struct_init => {
2871 const pl_node = data[@intFromEnum(inst)].pl_node;
2872 const extra = file.zir.extraData(Zir.Inst.StructInit, pl_node.payload_index);
2873 const field_vals = try self.arena.alloc(
2874 DocData.Expr.FieldVal,
2875 extra.data.fields_len,
2876 );
2877
2878 var type_ref: DocData.Expr = undefined;
2879 var idx = extra.end;
2880 for (field_vals) |*fv| {
2881 const init_extra = file.zir.extraData(Zir.Inst.StructInit.Item, idx);
2882 defer idx = init_extra.end;
2883
2884 const field_name = blk: {
2885 const field_inst_index = @intFromEnum(init_extra.data.field_type);
2886 if (tags[field_inst_index] != .struct_init_field_type) unreachable;
2887 const field_pl_node = data[field_inst_index].pl_node;
2888 const field_extra = file.zir.extraData(
2889 Zir.Inst.FieldType,
2890 field_pl_node.payload_index,
2891 );
2892 const field_src = try self.srcLocInfo(
2893 file,
2894 field_pl_node.src_node,
2895 parent_src,
2896 );
2897
2898 // On first iteration use field info to find out the struct type
2899 if (idx == extra.end) {
2900 const wr = try self.walkRef(
2901 file,
2902 parent_scope,
2903 field_src,
2904 field_extra.data.container_type,
2905 false,
2906 call_ctx,
2907 );
2908 type_ref = wr.expr;
2909 }
2910 break :blk file.zir.nullTerminatedString(field_extra.data.name_start);
2911 };
2912 const value = try self.walkRef(
2913 file,
2914 parent_scope,
2915 parent_src,
2916 init_extra.data.init,
2917 need_type,
2918 call_ctx,
2919 );
2920 const exprIdx = self.exprs.items.len;
2921 try self.exprs.append(self.arena, value.expr);
2922 var typeRefIdx: ?usize = null;
2923 if (value.typeRef) |ref| {
2924 typeRefIdx = self.exprs.items.len;
2925 try self.exprs.append(self.arena, ref);
2926 }
2927 fv.* = .{
2928 .name = field_name,
2929 .val = .{
2930 .typeRef = typeRefIdx,
2931 .expr = exprIdx,
2932 },
2933 };
2934 }
2935
2936 return DocData.WalkResult{
2937 .typeRef = type_ref,
2938 .expr = .{ .@"struct" = field_vals },
2939 };
2940 },
2941 .struct_init_empty,
2942 .struct_init_empty_result,
2943 => {
2944 const un_node = data[@intFromEnum(inst)].un_node;
2945
2946 const operand: DocData.WalkResult = try self.walkRef(
2947 file,
2948 parent_scope,
2949 parent_src,
2950 un_node.operand,
2951 false,
2952 call_ctx,
2953 );
2954
2955 return DocData.WalkResult{
2956 .typeRef = operand.expr,
2957 .expr = .{ .@"struct" = &.{} },
2958 };
2959 },
2960 .struct_init_empty_ref_result => {
2961 const un_node = data[@intFromEnum(inst)].un_node;
2962
2963 const operand: DocData.WalkResult = try self.walkRef(
2964 file,
2965 parent_scope,
2966 parent_src,
2967 un_node.operand,
2968 false,
2969 call_ctx,
2970 );
2971
2972 const struct_init_idx = self.exprs.items.len;
2973 try self.exprs.append(self.arena, .{ .@"struct" = &.{} });
2974
2975 return DocData.WalkResult{
2976 .typeRef = operand.expr,
2977 .expr = .{ .@"&" = struct_init_idx },
2978 };
2979 },
2980 .struct_init_anon => {
2981 const pl_node = data[@intFromEnum(inst)].pl_node;
2982 const extra = file.zir.extraData(Zir.Inst.StructInitAnon, pl_node.payload_index);
2983
2984 const field_vals = try self.arena.alloc(
2985 DocData.Expr.FieldVal,
2986 extra.data.fields_len,
2987 );
2988
2989 var idx = extra.end;
2990 for (field_vals) |*fv| {
2991 const init_extra = file.zir.extraData(Zir.Inst.StructInitAnon.Item, idx);
2992 const field_name = file.zir.nullTerminatedString(init_extra.data.field_name);
2993 const value = try self.walkRef(
2994 file,
2995 parent_scope,
2996 parent_src,
2997 init_extra.data.init,
2998 need_type,
2999 call_ctx,
3000 );
3001
3002 const exprIdx = self.exprs.items.len;
3003 try self.exprs.append(self.arena, value.expr);
3004 var typeRefIdx: ?usize = null;
3005 if (value.typeRef) |ref| {
3006 typeRefIdx = self.exprs.items.len;
3007 try self.exprs.append(self.arena, ref);
3008 }
3009
3010 fv.* = .{
3011 .name = field_name,
3012 .val = .{
3013 .typeRef = typeRefIdx,
3014 .expr = exprIdx,
3015 },
3016 };
3017
3018 idx = init_extra.end;
3019 }
3020
3021 return DocData.WalkResult{
3022 .expr = .{ .@"struct" = field_vals },
3023 };
3024 },
3025 .error_set_decl => {
3026 const pl_node = data[@intFromEnum(inst)].pl_node;
3027 const extra = file.zir.extraData(Zir.Inst.ErrorSetDecl, pl_node.payload_index);
3028 const fields = try self.arena.alloc(
3029 DocData.Type.Field,
3030 extra.data.fields_len,
3031 );
3032 var idx = extra.end;
3033 for (fields) |*f| {
3034 const name = file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[idx]));
3035 idx += 1;
3036
3037 const docs = file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[idx]));
3038 idx += 1;
3039
3040 f.* = .{
3041 .name = name,
3042 .docs = docs,
3043 };
3044 }
3045
3046 const type_slot_index = self.types.items.len;
3047 try self.types.append(self.arena, .{
3048 .ErrorSet = .{
3049 .name = "todo errset",
3050 .fields = fields,
3051 },
3052 });
3053
3054 return DocData.WalkResult{
3055 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
3056 .expr = .{ .type = type_slot_index },
3057 };
3058 },
3059 .param_anytype, .param_anytype_comptime => {
3060 // @check if .param_anytype_comptime can be here
3061 // Analysis of anytype function params happens in `.func`.
3062 // This switch case handles the case where an expression depends
3063 // on an anytype field. E.g.: `fn foo(bar: anytype) @TypeOf(bar)`.
3064 // This means that we're looking at a generic expression.
3065 const str_tok = data[@intFromEnum(inst)].str_tok;
3066 const name = str_tok.get(file.zir);
3067 const cte_slot_index = self.comptime_exprs.items.len;
3068 try self.comptime_exprs.append(self.arena, .{
3069 .code = name,
3070 });
3071 return DocData.WalkResult{ .expr = .{ .comptimeExpr = cte_slot_index } };
3072 },
3073 .param, .param_comptime => {
3074 // See .param_anytype for more information.
3075 const pl_tok = data[@intFromEnum(inst)].pl_tok;
3076 const extra = file.zir.extraData(Zir.Inst.Param, pl_tok.payload_index);
3077 const name = file.zir.nullTerminatedString(extra.data.name);
3078
3079 const cte_slot_index = self.comptime_exprs.items.len;
3080 try self.comptime_exprs.append(self.arena, .{
3081 .code = name,
3082 });
3083 return DocData.WalkResult{ .expr = .{ .comptimeExpr = cte_slot_index } };
3084 },
3085 .call => {
3086 const pl_node = data[@intFromEnum(inst)].pl_node;
3087 const extra = file.zir.extraData(Zir.Inst.Call, pl_node.payload_index);
3088
3089 const callee = try self.walkRef(
3090 file,
3091 parent_scope,
3092 parent_src,
3093 extra.data.callee,
3094 need_type,
3095 call_ctx,
3096 );
3097
3098 const args_len = extra.data.flags.args_len;
3099 var args = try self.arena.alloc(DocData.Expr, args_len);
3100 const body = file.zir.extra[extra.end..];
3101
3102 try self.repurposed_insts.put(self.arena, inst, {});
3103 defer _ = self.repurposed_insts.remove(inst);
3104
3105 var i: usize = 0;
3106 while (i < args_len) : (i += 1) {
3107 const arg_end = file.zir.extra[extra.end + i];
3108 const break_index = body[arg_end - 1];
3109 const ref = data[break_index].@"break".operand;
3110 // TODO: consider toggling need_type to true if we ever want
3111 // to show discrepancies between the types of provided
3112 // arguments and the types declared in the function
3113 // signature for its parameters.
3114 const wr = try self.walkRef(
3115 file,
3116 parent_scope,
3117 parent_src,
3118 ref,
3119 false,
3120 &.{
3121 .inst = inst,
3122 .prev = call_ctx,
3123 },
3124 );
3125 args[i] = wr.expr;
3126 }
3127
3128 const cte_slot_index = self.comptime_exprs.items.len;
3129 try self.comptime_exprs.append(self.arena, .{
3130 .code = "func call",
3131 });
3132
3133 const call_slot_index = self.calls.items.len;
3134 try self.calls.append(self.arena, .{
3135 .func = callee.expr,
3136 .args = args,
3137 .ret = .{ .comptimeExpr = cte_slot_index },
3138 });
3139
3140 return DocData.WalkResult{
3141 .typeRef = if (callee.typeRef) |tr| switch (tr) {
3142 .type => |func_type_idx| switch (self.types.items[func_type_idx]) {
3143 .Fn => |func| func.ret,
3144 else => blk: {
3145 printWithContext(
3146 file,
3147 inst,
3148 "unexpected callee type in walkInstruction.call: `{s}`\n",
3149 .{@tagName(self.types.items[func_type_idx])},
3150 );
3151
3152 break :blk null;
3153 },
3154 },
3155 else => null,
3156 } else null,
3157 .expr = .{ .call = call_slot_index },
3158 };
3159 },
3160 .field_call => {
3161 const pl_node = data[@intFromEnum(inst)].pl_node;
3162 const extra = file.zir.extraData(Zir.Inst.FieldCall, pl_node.payload_index);
3163
3164 const obj_ptr = try self.walkRef(
3165 file,
3166 parent_scope,
3167 parent_src,
3168 extra.data.obj_ptr,
3169 need_type,
3170 call_ctx,
3171 );
3172
3173 var field_call = try self.arena.alloc(DocData.Expr, 2);
3174
3175 if (obj_ptr.typeRef) |ref| {
3176 field_call[0] = ref;
3177 } else {
3178 field_call[0] = obj_ptr.expr;
3179 }
3180 field_call[1] = .{ .declName = file.zir.nullTerminatedString(extra.data.field_name_start) };
3181 try self.tryResolveRefPath(file, inst, field_call);
3182
3183 const args_len = extra.data.flags.args_len;
3184 var args = try self.arena.alloc(DocData.Expr, args_len);
3185 const body = file.zir.extra[extra.end..];
3186
3187 try self.repurposed_insts.put(self.arena, inst, {});
3188 defer _ = self.repurposed_insts.remove(inst);
3189
3190 var i: usize = 0;
3191 while (i < args_len) : (i += 1) {
3192 const arg_end = file.zir.extra[extra.end + i];
3193 const break_index = body[arg_end - 1];
3194 const ref = data[break_index].@"break".operand;
3195 // TODO: consider toggling need_type to true if we ever want
3196 // to show discrepancies between the types of provided
3197 // arguments and the types declared in the function
3198 // signature for its parameters.
3199 const wr = try self.walkRef(
3200 file,
3201 parent_scope,
3202 parent_src,
3203 ref,
3204 false,
3205 &.{
3206 .inst = inst,
3207 .prev = call_ctx,
3208 },
3209 );
3210 args[i] = wr.expr;
3211 }
3212
3213 const cte_slot_index = self.comptime_exprs.items.len;
3214 try self.comptime_exprs.append(self.arena, .{
3215 .code = "field call",
3216 });
3217
3218 const call_slot_index = self.calls.items.len;
3219 try self.calls.append(self.arena, .{
3220 .func = .{ .refPath = field_call },
3221 .args = args,
3222 .ret = .{ .comptimeExpr = cte_slot_index },
3223 });
3224
3225 return DocData.WalkResult{
3226 .expr = .{ .call = call_slot_index },
3227 };
3228 },
3229 .func, .func_inferred => {
3230 const type_slot_index = self.types.items.len;
3231 try self.types.append(self.arena, .{ .Unanalyzed = .{} });
3232
3233 const result = self.analyzeFunction(
3234 file,
3235 parent_scope,
3236 parent_src,
3237 inst,
3238 self_ast_node_index,
3239 type_slot_index,
3240 tags[@intFromEnum(inst)] == .func_inferred,
3241 call_ctx,
3242 );
3243
3244 return result;
3245 },
3246 .func_fancy => {
3247 const type_slot_index = self.types.items.len;
3248 try self.types.append(self.arena, .{ .Unanalyzed = .{} });
3249
3250 const result = self.analyzeFancyFunction(
3251 file,
3252 parent_scope,
3253 parent_src,
3254 inst,
3255 self_ast_node_index,
3256 type_slot_index,
3257 call_ctx,
3258 );
3259
3260 return result;
3261 },
3262 .optional_payload_safe, .optional_payload_unsafe => {
3263 const un_node = data[@intFromEnum(inst)].un_node;
3264 const operand = try self.walkRef(
3265 file,
3266 parent_scope,
3267 parent_src,
3268 un_node.operand,
3269 need_type,
3270 call_ctx,
3271 );
3272 const optional_idx = self.exprs.items.len;
3273 try self.exprs.append(self.arena, operand.expr);
3274
3275 var typeRef: ?DocData.Expr = null;
3276 if (operand.typeRef) |ref| {
3277 switch (ref) {
3278 .type => |t_index| {
3279 const t = self.types.items[t_index];
3280 switch (t) {
3281 .Optional => |opt| typeRef = opt.child,
3282 else => {
3283 printWithContext(file, inst, "Invalid type for optional_payload_*: {}\n", .{t});
3284 },
3285 }
3286 },
3287 else => {},
3288 }
3289 }
3290
3291 return DocData.WalkResult{
3292 .typeRef = typeRef,
3293 .expr = .{ .optionalPayload = optional_idx },
3294 };
3295 },
3296 .elem_val_node => {
3297 const pl_node = data[@intFromEnum(inst)].pl_node;
3298 const extra = file.zir.extraData(Zir.Inst.Bin, pl_node.payload_index);
3299 const lhs = try self.walkRef(
3300 file,
3301 parent_scope,
3302 parent_src,
3303 extra.data.lhs,
3304 need_type,
3305 call_ctx,
3306 );
3307 const rhs = try self.walkRef(
3308 file,
3309 parent_scope,
3310 parent_src,
3311 extra.data.rhs,
3312 need_type,
3313 call_ctx,
3314 );
3315 const lhs_idx = self.exprs.items.len;
3316 try self.exprs.append(self.arena, lhs.expr);
3317 const rhs_idx = self.exprs.items.len;
3318 try self.exprs.append(self.arena, rhs.expr);
3319 return DocData.WalkResult{
3320 .expr = .{
3321 .elemVal = .{
3322 .lhs = lhs_idx,
3323 .rhs = rhs_idx,
3324 },
3325 },
3326 };
3327 },
3328 .extended => {
3329 const extended = data[@intFromEnum(inst)].extended;
3330 switch (extended.opcode) {
3331 else => {
3332 printWithContext(
3333 file,
3334 inst,
3335 "TODO: implement `walkInstruction.extended` for {s}",
3336 .{@tagName(extended.opcode)},
3337 );
3338 return self.cteTodo(@tagName(extended.opcode));
3339 },
3340 .typeof_peer => {
3341 // Zir says it's a NodeMultiOp but in this case it's TypeOfPeer
3342 const extra = file.zir.extraData(Zir.Inst.TypeOfPeer, extended.operand);
3343 const args = file.zir.refSlice(extra.end, extended.small);
3344 const array_data = try self.arena.alloc(usize, args.len);
3345
3346 var array_type: ?DocData.Expr = null;
3347 for (args, 0..) |arg, idx| {
3348 const wr = try self.walkRef(
3349 file,
3350 parent_scope,
3351 parent_src,
3352 arg,
3353 idx == 0,
3354 call_ctx,
3355 );
3356 if (idx == 0) {
3357 array_type = wr.typeRef;
3358 }
3359
3360 const expr_index = self.exprs.items.len;
3361 try self.exprs.append(self.arena, wr.expr);
3362 array_data[idx] = expr_index;
3363 }
3364
3365 const type_slot_index = self.types.items.len;
3366 try self.types.append(self.arena, .{
3367 .Array = .{
3368 .len = .{
3369 .int = .{
3370 .value = args.len,
3371 .negated = false,
3372 },
3373 },
3374 .child = .{ .type = 0 },
3375 },
3376 });
3377 const result = DocData.WalkResult{
3378 .typeRef = .{ .type = type_slot_index },
3379 .expr = .{ .typeOf_peer = array_data },
3380 };
3381
3382 return result;
3383 },
3384 .opaque_decl => {
3385 const type_slot_index = self.types.items.len;
3386 try self.types.append(self.arena, .{ .Unanalyzed = .{} });
3387
3388 var scope: Scope = .{
3389 .parent = parent_scope,
3390 .enclosing_type = type_slot_index,
3391 };
3392
3393 const small: Zir.Inst.OpaqueDecl.Small = @bitCast(extended.small);
3394 const extra = file.zir.extraData(Zir.Inst.OpaqueDecl, extended.operand);
3395 var extra_index: usize = extra.end;
3396
3397 const src_info = try self.srcLocInfo(file, extra.data.src_node, parent_src);
3398
3399 const captures_len = if (small.has_captures_len) blk: {
3400 const captures_len = file.zir.extra[extra_index];
3401 extra_index += 1;
3402 break :blk captures_len;
3403 } else 0;
3404
3405 if (small.has_decls_len) extra_index += 1;
3406
3407 scope.captures = @ptrCast(file.zir.extra[extra_index..][0..captures_len]);
3408 extra_index += captures_len;
3409
3410 var decl_indexes: std.ArrayListUnmanaged(usize) = .{};
3411 var priv_decl_indexes: std.ArrayListUnmanaged(usize) = .{};
3412
3413 extra_index = try self.analyzeAllDecls(
3414 file,
3415 &scope,
3416 inst,
3417 src_info,
3418 &decl_indexes,
3419 &priv_decl_indexes,
3420 call_ctx,
3421 );
3422
3423 self.types.items[type_slot_index] = .{
3424 .Opaque = .{
3425 .name = "todo_name",
3426 .src = self_ast_node_index,
3427 .privDecls = priv_decl_indexes.items,
3428 .pubDecls = decl_indexes.items,
3429 .parent_container = parent_scope.enclosing_type,
3430 },
3431 };
3432 if (self.ref_paths_pending_on_types.get(type_slot_index)) |paths| {
3433 for (paths.items) |resume_info| {
3434 try self.tryResolveRefPath(
3435 resume_info.file,
3436 inst,
3437 resume_info.ref_path,
3438 );
3439 }
3440
3441 _ = self.ref_paths_pending_on_types.remove(type_slot_index);
3442 // TODO: we should deallocate the arraylist that holds all the
3443 // decl paths. not doing it now since it's arena-allocated
3444 // anyway, but maybe we should put it elsewhere.
3445 }
3446 return DocData.WalkResult{
3447 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
3448 .expr = .{ .type = type_slot_index },
3449 };
3450 },
3451 .variable => {
3452 const extra = file.zir.extraData(Zir.Inst.ExtendedVar, extended.operand);
3453
3454 const small = @as(Zir.Inst.ExtendedVar.Small, @bitCast(extended.small));
3455 var extra_index: usize = extra.end;
3456 if (small.has_lib_name) extra_index += 1;
3457 if (small.has_align) extra_index += 1;
3458
3459 const var_type = try self.walkRef(
3460 file,
3461 parent_scope,
3462 parent_src,
3463 extra.data.var_type,
3464 need_type,
3465 call_ctx,
3466 );
3467
3468 var value: DocData.WalkResult = .{
3469 .typeRef = var_type.expr,
3470 .expr = .{ .undefined = .{} },
3471 };
3472
3473 if (small.has_init) {
3474 const var_init_ref = @as(Ref, @enumFromInt(file.zir.extra[extra_index]));
3475 const var_init = try self.walkRef(
3476 file,
3477 parent_scope,
3478 parent_src,
3479 var_init_ref,
3480 need_type,
3481 call_ctx,
3482 );
3483 value.expr = var_init.expr;
3484 value.typeRef = var_init.typeRef;
3485 }
3486
3487 return value;
3488 },
3489 .union_decl => {
3490 const type_slot_index = self.types.items.len;
3491 try self.types.append(self.arena, .{ .Unanalyzed = .{} });
3492
3493 var scope: Scope = .{
3494 .parent = parent_scope,
3495 .enclosing_type = type_slot_index,
3496 };
3497
3498 const small = @as(Zir.Inst.UnionDecl.Small, @bitCast(extended.small));
3499 const extra = file.zir.extraData(Zir.Inst.UnionDecl, extended.operand);
3500 var extra_index: usize = extra.end;
3501
3502 const src_info = try self.srcLocInfo(file, extra.data.src_node, parent_src);
3503
3504 // We delay analysis because union tags can refer to
3505 // decls defined inside the union itself.
3506 const tag_type_ref: ?Ref = if (small.has_tag_type) blk: {
3507 const tag_type = file.zir.extra[extra_index];
3508 extra_index += 1;
3509 const tag_ref = @as(Ref, @enumFromInt(tag_type));
3510 break :blk tag_ref;
3511 } else null;
3512
3513 const captures_len = if (small.has_captures_len) blk: {
3514 const captures_len = file.zir.extra[extra_index];
3515 extra_index += 1;
3516 break :blk captures_len;
3517 } else 0;
3518
3519 const body_len = if (small.has_body_len) blk: {
3520 const body_len = file.zir.extra[extra_index];
3521 extra_index += 1;
3522 break :blk body_len;
3523 } else 0;
3524
3525 const fields_len = if (small.has_fields_len) blk: {
3526 const fields_len = file.zir.extra[extra_index];
3527 extra_index += 1;
3528 break :blk fields_len;
3529 } else 0;
3530
3531 const layout_expr: ?DocData.Expr = switch (small.layout) {
3532 .Auto => null,
3533 else => .{ .enumLiteral = @tagName(small.layout) },
3534 };
3535
3536 if (small.has_decls_len) extra_index += 1;
3537
3538 scope.captures = @ptrCast(file.zir.extra[extra_index..][0..captures_len]);
3539 extra_index += captures_len;
3540
3541 var decl_indexes: std.ArrayListUnmanaged(usize) = .{};
3542 var priv_decl_indexes: std.ArrayListUnmanaged(usize) = .{};
3543
3544 extra_index = try self.analyzeAllDecls(
3545 file,
3546 &scope,
3547 inst,
3548 src_info,
3549 &decl_indexes,
3550 &priv_decl_indexes,
3551 call_ctx,
3552 );
3553
3554 // Analyze the tag once all decls have been analyzed
3555 const tag_type = if (tag_type_ref) |tt_ref| (try self.walkRef(
3556 file,
3557 &scope,
3558 parent_src,
3559 tt_ref,
3560 false,
3561 call_ctx,
3562 )).expr else null;
3563
3564 // Fields
3565 extra_index += body_len;
3566
3567 var field_type_refs = try std.ArrayListUnmanaged(DocData.Expr).initCapacity(
3568 self.arena,
3569 fields_len,
3570 );
3571 var field_name_indexes = try std.ArrayListUnmanaged(usize).initCapacity(
3572 self.arena,
3573 fields_len,
3574 );
3575 try self.collectUnionFieldInfo(
3576 file,
3577 &scope,
3578 src_info,
3579 fields_len,
3580 &field_type_refs,
3581 &field_name_indexes,
3582 extra_index,
3583 call_ctx,
3584 );
3585
3586 self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
3587
3588 self.types.items[type_slot_index] = .{
3589 .Union = .{
3590 .name = "todo_name",
3591 .src = self_ast_node_index,
3592 .privDecls = priv_decl_indexes.items,
3593 .pubDecls = decl_indexes.items,
3594 .fields = field_type_refs.items,
3595 .tag = tag_type,
3596 .auto_enum = small.auto_enum_tag,
3597 .parent_container = parent_scope.enclosing_type,
3598 .layout = layout_expr,
3599 },
3600 };
3601
3602 if (self.ref_paths_pending_on_types.get(type_slot_index)) |paths| {
3603 for (paths.items) |resume_info| {
3604 try self.tryResolveRefPath(
3605 resume_info.file,
3606 inst,
3607 resume_info.ref_path,
3608 );
3609 }
3610
3611 _ = self.ref_paths_pending_on_types.remove(type_slot_index);
3612 // TODO: we should deallocate the arraylist that holds all the
3613 // decl paths. not doing it now since it's arena-allocated
3614 // anyway, but maybe we should put it elsewhere.
3615 }
3616
3617 return DocData.WalkResult{
3618 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
3619 .expr = .{ .type = type_slot_index },
3620 };
3621 },
3622 .enum_decl => {
3623 const type_slot_index = self.types.items.len;
3624 try self.types.append(self.arena, .{ .Unanalyzed = .{} });
3625
3626 var scope: Scope = .{
3627 .parent = parent_scope,
3628 .enclosing_type = type_slot_index,
3629 };
3630
3631 const small = @as(Zir.Inst.EnumDecl.Small, @bitCast(extended.small));
3632 const extra = file.zir.extraData(Zir.Inst.EnumDecl, extended.operand);
3633 var extra_index: usize = extra.end;
3634
3635 const src_info = try self.srcLocInfo(file, extra.data.src_node, parent_src);
3636
3637 const tag_type: ?DocData.Expr = if (small.has_tag_type) blk: {
3638 const tag_type = file.zir.extra[extra_index];
3639 extra_index += 1;
3640 const tag_ref = @as(Ref, @enumFromInt(tag_type));
3641 const wr = try self.walkRef(
3642 file,
3643 parent_scope,
3644 parent_src,
3645 tag_ref,
3646 false,
3647 call_ctx,
3648 );
3649 break :blk wr.expr;
3650 } else null;
3651
3652 const captures_len = if (small.has_captures_len) blk: {
3653 const captures_len = file.zir.extra[extra_index];
3654 extra_index += 1;
3655 break :blk captures_len;
3656 } else 0;
3657
3658 const body_len = if (small.has_body_len) blk: {
3659 const body_len = file.zir.extra[extra_index];
3660 extra_index += 1;
3661 break :blk body_len;
3662 } else 0;
3663
3664 const fields_len = if (small.has_fields_len) blk: {
3665 const fields_len = file.zir.extra[extra_index];
3666 extra_index += 1;
3667 break :blk fields_len;
3668 } else 0;
3669
3670 if (small.has_decls_len) extra_index += 1;
3671
3672 scope.captures = @ptrCast(file.zir.extra[extra_index..][0..captures_len]);
3673 extra_index += captures_len;
3674
3675 var decl_indexes: std.ArrayListUnmanaged(usize) = .{};
3676 var priv_decl_indexes: std.ArrayListUnmanaged(usize) = .{};
3677
3678 extra_index = try self.analyzeAllDecls(
3679 file,
3680 &scope,
3681 inst,
3682 src_info,
3683 &decl_indexes,
3684 &priv_decl_indexes,
3685 call_ctx,
3686 );
3687
3688 // const body = file.zir.extra[extra_index..][0..body_len];
3689 extra_index += body_len;
3690
3691 var field_name_indexes: std.ArrayListUnmanaged(usize) = .{};
3692 var field_values: std.ArrayListUnmanaged(?DocData.Expr) = .{};
3693 {
3694 var bit_bag_idx = extra_index;
3695 var cur_bit_bag: u32 = undefined;
3696 extra_index += std.math.divCeil(usize, fields_len, 32) catch unreachable;
3697
3698 var idx: usize = 0;
3699 while (idx < fields_len) : (idx += 1) {
3700 if (idx % 32 == 0) {
3701 cur_bit_bag = file.zir.extra[bit_bag_idx];
3702 bit_bag_idx += 1;
3703 }
3704
3705 const has_value = @as(u1, @truncate(cur_bit_bag)) != 0;
3706 cur_bit_bag >>= 1;
3707
3708 const field_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
3709 extra_index += 1;
3710
3711 const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
3712 extra_index += 1;
3713
3714 const value_expr: ?DocData.Expr = if (has_value) blk: {
3715 const value_ref = file.zir.extra[extra_index];
3716 extra_index += 1;
3717 const value = try self.walkRef(
3718 file,
3719 &scope,
3720 src_info,
3721 @as(Ref, @enumFromInt(value_ref)),
3722 false,
3723 call_ctx,
3724 );
3725 break :blk value.expr;
3726 } else null;
3727 try field_values.append(self.arena, value_expr);
3728
3729 const field_name = file.zir.nullTerminatedString(field_name_index);
3730
3731 try field_name_indexes.append(self.arena, self.ast_nodes.items.len);
3732 const doc_comment: ?[]const u8 = if (doc_comment_index != .empty)
3733 file.zir.nullTerminatedString(doc_comment_index)
3734 else
3735 null;
3736 try self.ast_nodes.append(self.arena, .{
3737 .name = field_name,
3738 .docs = doc_comment,
3739 });
3740 }
3741 }
3742
3743 self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
3744
3745 self.types.items[type_slot_index] = .{
3746 .Enum = .{
3747 .name = "todo_name",
3748 .src = self_ast_node_index,
3749 .privDecls = priv_decl_indexes.items,
3750 .pubDecls = decl_indexes.items,
3751 .tag = tag_type,
3752 .values = field_values.items,
3753 .nonexhaustive = small.nonexhaustive,
3754 .parent_container = parent_scope.enclosing_type,
3755 },
3756 };
3757 if (self.ref_paths_pending_on_types.get(type_slot_index)) |paths| {
3758 for (paths.items) |resume_info| {
3759 try self.tryResolveRefPath(
3760 resume_info.file,
3761 inst,
3762 resume_info.ref_path,
3763 );
3764 }
3765
3766 _ = self.ref_paths_pending_on_types.remove(type_slot_index);
3767 // TODO: we should deallocate the arraylist that holds all the
3768 // decl paths. not doing it now since it's arena-allocated
3769 // anyway, but maybe we should put it elsewhere.
3770 }
3771 return DocData.WalkResult{
3772 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
3773 .expr = .{ .type = type_slot_index },
3774 };
3775 },
3776 .struct_decl => {
3777 const type_slot_index = self.types.items.len;
3778 try self.types.append(self.arena, .{ .Unanalyzed = .{} });
3779
3780 var scope: Scope = .{
3781 .parent = parent_scope,
3782 .enclosing_type = type_slot_index,
3783 };
3784
3785 const small = @as(Zir.Inst.StructDecl.Small, @bitCast(extended.small));
3786 const extra = file.zir.extraData(Zir.Inst.StructDecl, extended.operand);
3787 var extra_index: usize = extra.end;
3788
3789 const src_info = try self.srcLocInfo(file, extra.data.src_node, parent_src);
3790
3791 const captures_len = if (small.has_captures_len) blk: {
3792 const captures_len = file.zir.extra[extra_index];
3793 extra_index += 1;
3794 break :blk captures_len;
3795 } else 0;
3796
3797 const fields_len = if (small.has_fields_len) blk: {
3798 const fields_len = file.zir.extra[extra_index];
3799 extra_index += 1;
3800 break :blk fields_len;
3801 } else 0;
3802
3803 // We don't care about decls yet
3804 if (small.has_decls_len) extra_index += 1;
3805
3806 scope.captures = @ptrCast(file.zir.extra[extra_index..][0..captures_len]);
3807 extra_index += captures_len;
3808
3809 var backing_int: ?DocData.Expr = null;
3810 if (small.has_backing_int) {
3811 const backing_int_body_len = file.zir.extra[extra_index];
3812 extra_index += 1; // backing_int_body_len
3813 if (backing_int_body_len == 0) {
3814 const backing_int_ref = @as(Ref, @enumFromInt(file.zir.extra[extra_index]));
3815 const backing_int_res = try self.walkRef(
3816 file,
3817 &scope,
3818 src_info,
3819 backing_int_ref,
3820 true,
3821 call_ctx,
3822 );
3823 backing_int = backing_int_res.expr;
3824 extra_index += 1; // backing_int_ref
3825 } else {
3826 const backing_int_body = file.zir.bodySlice(extra_index, backing_int_body_len);
3827 const break_inst = backing_int_body[backing_int_body.len - 1];
3828 const operand = data[@intFromEnum(break_inst)].@"break".operand;
3829 const backing_int_res = try self.walkRef(
3830 file,
3831 &scope,
3832 src_info,
3833 operand,
3834 true,
3835 call_ctx,
3836 );
3837 backing_int = backing_int_res.expr;
3838 extra_index += backing_int_body_len; // backing_int_body_inst
3839 }
3840 }
3841
3842 const layout_expr: ?DocData.Expr = switch (small.layout) {
3843 .Auto => null,
3844 else => .{ .enumLiteral = @tagName(small.layout) },
3845 };
3846
3847 var decl_indexes: std.ArrayListUnmanaged(usize) = .{};
3848 var priv_decl_indexes: std.ArrayListUnmanaged(usize) = .{};
3849
3850 extra_index = try self.analyzeAllDecls(
3851 file,
3852 &scope,
3853 inst,
3854 src_info,
3855 &decl_indexes,
3856 &priv_decl_indexes,
3857 call_ctx,
3858 );
3859
3860 // Inside field init bodies, the struct decl instruction is used to refer to the
3861 // field type during the second pass of analysis.
3862 try self.repurposed_insts.put(self.arena, inst, {});
3863 defer _ = self.repurposed_insts.remove(inst);
3864
3865 var field_type_refs: std.ArrayListUnmanaged(DocData.Expr) = .{};
3866 var field_default_refs: std.ArrayListUnmanaged(?DocData.Expr) = .{};
3867 var field_name_indexes: std.ArrayListUnmanaged(usize) = .{};
3868 try self.collectStructFieldInfo(
3869 file,
3870 &scope,
3871 src_info,
3872 fields_len,
3873 &field_type_refs,
3874 &field_default_refs,
3875 &field_name_indexes,
3876 extra_index,
3877 small.is_tuple,
3878 call_ctx,
3879 );
3880
3881 self.ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
3882
3883 self.types.items[type_slot_index] = .{
3884 .Struct = .{
3885 .name = "todo_name",
3886 .src = self_ast_node_index,
3887 .privDecls = priv_decl_indexes.items,
3888 .pubDecls = decl_indexes.items,
3889 .field_types = field_type_refs.items,
3890 .field_defaults = field_default_refs.items,
3891 .is_tuple = small.is_tuple,
3892 .backing_int = backing_int,
3893 .line_number = self.ast_nodes.items[self_ast_node_index].line,
3894 .parent_container = parent_scope.enclosing_type,
3895 .layout = layout_expr,
3896 },
3897 };
3898 if (self.ref_paths_pending_on_types.get(type_slot_index)) |paths| {
3899 for (paths.items) |resume_info| {
3900 try self.tryResolveRefPath(
3901 resume_info.file,
3902 inst,
3903 resume_info.ref_path,
3904 );
3905 }
3906
3907 _ = self.ref_paths_pending_on_types.remove(type_slot_index);
3908 // TODO: we should deallocate the arraylist that holds all the
3909 // decl paths. not doing it now since it's arena-allocated
3910 // anyway, but maybe we should put it elsewhere.
3911 }
3912 return DocData.WalkResult{
3913 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
3914 .expr = .{ .type = type_slot_index },
3915 };
3916 },
3917 .this => {
3918 return DocData.WalkResult{
3919 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
3920 .expr = .{
3921 .this = parent_scope.enclosing_type.?,
3922 // We know enclosing_type is always present
3923 // because it's only null for the top-level
3924 // struct instruction of a file.
3925 },
3926 };
3927 },
3928 .int_from_error,
3929 .error_from_int,
3930 .reify,
3931 => {
3932 const extra = file.zir.extraData(Zir.Inst.UnNode, extended.operand).data;
3933 const bin_index = self.exprs.items.len;
3934 try self.exprs.append(self.arena, .{ .builtin = .{ .param = 0 } });
3935 const param = try self.walkRef(
3936 file,
3937 parent_scope,
3938 parent_src,
3939 extra.operand,
3940 false,
3941 call_ctx,
3942 );
3943
3944 const param_index = self.exprs.items.len;
3945 try self.exprs.append(self.arena, param.expr);
3946
3947 self.exprs.items[bin_index] = .{ .builtin = .{ .name = @tagName(extended.opcode), .param = param_index } };
3948
3949 return DocData.WalkResult{
3950 .typeRef = param.typeRef orelse .{ .type = @intFromEnum(Ref.type_type) },
3951 .expr = .{ .builtinIndex = bin_index },
3952 };
3953 },
3954 .work_item_id,
3955 .work_group_size,
3956 .work_group_id,
3957 => {
3958 const extra = file.zir.extraData(Zir.Inst.UnNode, extended.operand).data;
3959 const bin_index = self.exprs.items.len;
3960 try self.exprs.append(self.arena, .{ .builtin = .{ .param = 0 } });
3961 const param = try self.walkRef(
3962 file,
3963 parent_scope,
3964 parent_src,
3965 extra.operand,
3966 false,
3967 call_ctx,
3968 );
3969
3970 const param_index = self.exprs.items.len;
3971 try self.exprs.append(self.arena, param.expr);
3972
3973 self.exprs.items[bin_index] = .{ .builtin = .{ .name = @tagName(extended.opcode), .param = param_index } };
3974
3975 return DocData.WalkResult{
3976 // from docs we know they return u32
3977 .typeRef = .{ .type = @intFromEnum(Ref.u32_type) },
3978 .expr = .{ .builtinIndex = bin_index },
3979 };
3980 },
3981 .cmpxchg => {
3982 const extra = file.zir.extraData(Zir.Inst.Cmpxchg, extended.operand).data;
3983
3984 const last_type_index = self.exprs.items.len;
3985 const last_type = self.exprs.items[last_type_index - 1];
3986 const type_index = self.exprs.items.len;
3987 try self.exprs.append(self.arena, last_type);
3988
3989 const ptr_index = self.exprs.items.len;
3990 const ptr: DocData.WalkResult = try self.walkRef(
3991 file,
3992 parent_scope,
3993 parent_src,
3994 extra.ptr,
3995 false,
3996 call_ctx,
3997 );
3998 try self.exprs.append(self.arena, ptr.expr);
3999
4000 const expected_value_index = self.exprs.items.len;
4001 const expected_value: DocData.WalkResult = try self.walkRef(
4002 file,
4003 parent_scope,
4004 parent_src,
4005 extra.expected_value,
4006 false,
4007 call_ctx,
4008 );
4009 try self.exprs.append(self.arena, expected_value.expr);
4010
4011 const new_value_index = self.exprs.items.len;
4012 const new_value: DocData.WalkResult = try self.walkRef(
4013 file,
4014 parent_scope,
4015 parent_src,
4016 extra.new_value,
4017 false,
4018 call_ctx,
4019 );
4020 try self.exprs.append(self.arena, new_value.expr);
4021
4022 const success_order_index = self.exprs.items.len;
4023 const success_order: DocData.WalkResult = try self.walkRef(
4024 file,
4025 parent_scope,
4026 parent_src,
4027 extra.success_order,
4028 false,
4029 call_ctx,
4030 );
4031 try self.exprs.append(self.arena, success_order.expr);
4032
4033 const failure_order_index = self.exprs.items.len;
4034 const failure_order: DocData.WalkResult = try self.walkRef(
4035 file,
4036 parent_scope,
4037 parent_src,
4038 extra.failure_order,
4039 false,
4040 call_ctx,
4041 );
4042 try self.exprs.append(self.arena, failure_order.expr);
4043
4044 const cmpxchg_index = self.exprs.items.len;
4045 try self.exprs.append(self.arena, .{ .cmpxchg = .{
4046 .name = @tagName(tags[@intFromEnum(inst)]),
4047 .type = type_index,
4048 .ptr = ptr_index,
4049 .expected_value = expected_value_index,
4050 .new_value = new_value_index,
4051 .success_order = success_order_index,
4052 .failure_order = failure_order_index,
4053 } });
4054 return DocData.WalkResult{
4055 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
4056 .expr = .{ .cmpxchgIndex = cmpxchg_index },
4057 };
4058 },
4059 .closure_get => {
4060 const captured, const scope = parent_scope.getCapture(extended.small);
4061 switch (captured) {
4062 .inst => |cap_inst| return self.walkInstruction(file, scope, parent_src, cap_inst, need_type, call_ctx),
4063 .decl => |str| {
4064 const decl_status = parent_scope.resolveDeclName(str, file, inst.toOptional());
4065 return .{ .expr = .{ .declRef = decl_status } };
4066 },
4067 }
4068 },
4069 }
4070 },
4071 }
4072}
4073
4074/// Called by `walkInstruction` when encountering a container type.
4075/// Iterates over all decl definitions in its body and it also analyzes each
4076/// decl's body recursively by calling into `walkInstruction`.
4077///
4078/// Does not append to `self.decls` directly because `walkInstruction`
4079/// is expected to look-ahead scan all decls and reserve `body_len`
4080/// slots in `self.decls`, which are then filled out by this function.
4081fn analyzeAllDecls(
4082 self: *Autodoc,
4083 file: *File,
4084 scope: *Scope,
4085 parent_inst: Zir.Inst.Index,
4086 parent_src: SrcLocInfo,
4087 decl_indexes: *std.ArrayListUnmanaged(usize),
4088 priv_decl_indexes: *std.ArrayListUnmanaged(usize),
4089 call_ctx: ?*const CallContext,
4090) AutodocErrors!usize {
4091 const first_decl_indexes_slot = decl_indexes.items.len;
4092 const original_it = file.zir.declIterator(parent_inst);
4093
4094 // First loop to discover decl names
4095 {
4096 var it = original_it;
4097 while (it.next()) |zir_index| {
4098 const declaration, _ = file.zir.getDeclaration(zir_index);
4099 if (declaration.name.isNamedTest(file.zir)) continue;
4100 const decl_name = declaration.name.toString(file.zir) orelse continue;
4101 try scope.insertDeclRef(self.arena, decl_name, .Pending);
4102 }
4103 }
4104
4105 // Second loop to analyze `usingnamespace` decls
4106 {
4107 var it = original_it;
4108 var decl_indexes_slot = first_decl_indexes_slot;
4109 while (it.next()) |zir_index| : (decl_indexes_slot += 1) {
4110 const pl_node = file.zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node;
4111 const extra = file.zir.extraData(Zir.Inst.Declaration, pl_node.payload_index);
4112 if (extra.data.name != .@"usingnamespace") continue;
4113 try self.analyzeUsingnamespaceDecl(
4114 file,
4115 scope,
4116 try self.srcLocInfo(file, pl_node.src_node, parent_src),
4117 decl_indexes,
4118 priv_decl_indexes,
4119 extra.data,
4120 @intCast(extra.end),
4121 call_ctx,
4122 );
4123 }
4124 }
4125
4126 // Third loop to analyze all remaining decls
4127 {
4128 var it = original_it;
4129 while (it.next()) |zir_index| {
4130 const pl_node = file.zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node;
4131 const extra = file.zir.extraData(Zir.Inst.Declaration, pl_node.payload_index);
4132 switch (extra.data.name) {
4133 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
4134 _ => if (extra.data.name.isNamedTest(file.zir)) continue,
4135 }
4136 try self.analyzeDecl(
4137 file,
4138 scope,
4139 try self.srcLocInfo(file, pl_node.src_node, parent_src),
4140 decl_indexes,
4141 priv_decl_indexes,
4142 zir_index,
4143 extra.data,
4144 @intCast(extra.end),
4145 call_ctx,
4146 );
4147 }
4148 }
4149
4150 // Fourth loop to analyze decltests
4151 var it = original_it;
4152 while (it.next()) |zir_index| {
4153 const pl_node = file.zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node;
4154 const extra = file.zir.extraData(Zir.Inst.Declaration, pl_node.payload_index);
4155 if (extra.data.name != .decltest) continue;
4156 try self.analyzeDecltest(
4157 file,
4158 scope,
4159 try self.srcLocInfo(file, pl_node.src_node, parent_src),
4160 extra.data,
4161 @intCast(extra.end),
4162 );
4163 }
4164
4165 return it.extra_index;
4166}
4167
4168fn walkInlineBody(
4169 autodoc: *Autodoc,
4170 file: *File,
4171 scope: *Scope,
4172 block_src: SrcLocInfo,
4173 parent_src: SrcLocInfo,
4174 body: []const Zir.Inst.Index,
4175 need_type: bool,
4176 call_ctx: ?*const CallContext,
4177) AutodocErrors!DocData.WalkResult {
4178 const tags = file.zir.instructions.items(.tag);
4179 const break_inst = switch (tags[@intFromEnum(body[body.len - 1])]) {
4180 .condbr_inline => {
4181 // Unresolvable.
4182 const res: DocData.WalkResult = .{
4183 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
4184 .expr = .{ .comptimeExpr = autodoc.comptime_exprs.items.len },
4185 };
4186 const source = (try file.getTree(autodoc.zcu.gpa)).getNodeSource(block_src.src_node);
4187 try autodoc.comptime_exprs.append(autodoc.arena, .{
4188 .code = source,
4189 });
4190 return res;
4191 },
4192 .break_inline => body[body.len - 1],
4193 else => unreachable,
4194 };
4195 const break_data = file.zir.instructions.items(.data)[@intFromEnum(break_inst)].@"break";
4196 return autodoc.walkRef(file, scope, parent_src, break_data.operand, need_type, call_ctx);
4197}
4198
4199// Asserts the given decl is public
4200fn analyzeDecl(
4201 self: *Autodoc,
4202 file: *File,
4203 scope: *Scope,
4204 decl_src: SrcLocInfo,
4205 decl_indexes: *std.ArrayListUnmanaged(usize),
4206 priv_decl_indexes: *std.ArrayListUnmanaged(usize),
4207 decl_inst: Zir.Inst.Index,
4208 declaration: Zir.Inst.Declaration,
4209 extra_index: u32,
4210 call_ctx: ?*const CallContext,
4211) AutodocErrors!void {
4212 const bodies = declaration.getBodies(extra_index, file.zir);
4213 const name = file.zir.nullTerminatedString(declaration.name.toString(file.zir).?);
4214
4215 const doc_comment: ?[]const u8 = if (declaration.flags.has_doc_comment)
4216 file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[extra_index]))
4217 else
4218 null;
4219
4220 // astnode
4221 const ast_node_index = idx: {
4222 const idx = self.ast_nodes.items.len;
4223 try self.ast_nodes.append(self.arena, .{
4224 .file = self.files.getIndex(file).?,
4225 .line = decl_src.line,
4226 .col = 0,
4227 .docs = doc_comment,
4228 .fields = null, // walkInstruction will fill `fields` if necessary
4229 });
4230 break :idx idx;
4231 };
4232
4233 const walk_result = try self.walkInlineBody(
4234 file,
4235 scope,
4236 decl_src,
4237 decl_src,
4238 bodies.value_body,
4239 true,
4240 call_ctx,
4241 );
4242
4243 const tree = try file.getTree(self.zcu.gpa);
4244 const kind_token = tree.nodes.items(.main_token)[decl_src.src_node];
4245 const kind: []const u8 = switch (tree.tokens.items(.tag)[kind_token]) {
4246 .keyword_var => "var",
4247 else => "const",
4248 };
4249
4250 const decls_slot_index = self.decls.items.len;
4251 try self.decls.append(self.arena, .{
4252 .name = name,
4253 .src = ast_node_index,
4254 .value = walk_result,
4255 .kind = kind,
4256 .parent_container = scope.enclosing_type,
4257 });
4258
4259 if (declaration.flags.is_pub) {
4260 try decl_indexes.append(self.arena, decls_slot_index);
4261 } else {
4262 try priv_decl_indexes.append(self.arena, decls_slot_index);
4263 }
4264
4265 const decl_status_ptr = scope.resolveDeclName(declaration.name.toString(file.zir).?, file, .none);
4266 std.debug.assert(decl_status_ptr.* == .Pending);
4267 decl_status_ptr.* = .{ .Analyzed = decls_slot_index };
4268
4269 // Unblock any pending decl path that was waiting for this decl.
4270 if (self.ref_paths_pending_on_decls.get(decl_status_ptr)) |paths| {
4271 for (paths.items) |resume_info| {
4272 try self.tryResolveRefPath(
4273 resume_info.file,
4274 decl_inst,
4275 resume_info.ref_path,
4276 );
4277 }
4278
4279 _ = self.ref_paths_pending_on_decls.remove(decl_status_ptr);
4280 // TODO: we should deallocate the arraylist that holds all the
4281 // ref paths. not doing it now since it's arena-allocated
4282 // anyway, but maybe we should put it elsewhere.
4283 }
4284}
4285
4286fn analyzeUsingnamespaceDecl(
4287 self: *Autodoc,
4288 file: *File,
4289 scope: *Scope,
4290 decl_src: SrcLocInfo,
4291 decl_indexes: *std.ArrayListUnmanaged(usize),
4292 priv_decl_indexes: *std.ArrayListUnmanaged(usize),
4293 declaration: Zir.Inst.Declaration,
4294 extra_index: u32,
4295 call_ctx: ?*const CallContext,
4296) AutodocErrors!void {
4297 const bodies = declaration.getBodies(extra_index, file.zir);
4298
4299 const doc_comment: ?[]const u8 = if (declaration.flags.has_doc_comment)
4300 file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[extra_index]))
4301 else
4302 null;
4303
4304 // astnode
4305 const ast_node_index = idx: {
4306 const idx = self.ast_nodes.items.len;
4307 try self.ast_nodes.append(self.arena, .{
4308 .file = self.files.getIndex(file).?,
4309 .line = decl_src.line,
4310 .col = 0,
4311 .docs = doc_comment,
4312 .fields = null, // walkInstruction will fill `fields` if necessary
4313 });
4314 break :idx idx;
4315 };
4316
4317 const walk_result = try self.walkInlineBody(
4318 file,
4319 scope,
4320 decl_src,
4321 decl_src,
4322 bodies.value_body,
4323 true,
4324 call_ctx,
4325 );
4326
4327 const decl_slot_index = self.decls.items.len;
4328 try self.decls.append(self.arena, .{
4329 .name = "",
4330 .kind = "",
4331 .src = ast_node_index,
4332 .value = walk_result,
4333 .is_uns = true,
4334 .parent_container = scope.enclosing_type,
4335 });
4336
4337 if (declaration.flags.is_pub) {
4338 try decl_indexes.append(self.arena, decl_slot_index);
4339 } else {
4340 try priv_decl_indexes.append(self.arena, decl_slot_index);
4341 }
4342}
4343
4344fn analyzeDecltest(
4345 self: *Autodoc,
4346 file: *File,
4347 scope: *Scope,
4348 decl_src: SrcLocInfo,
4349 declaration: Zir.Inst.Declaration,
4350 extra_index: u32,
4351) AutodocErrors!void {
4352 std.debug.assert(declaration.flags.has_doc_comment);
4353 const decl_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
4354
4355 const test_source_code = (try file.getTree(self.zcu.gpa)).getNodeSource(decl_src.src_node);
4356
4357 const decl_name: ?[]const u8 = if (decl_name_index != .empty)
4358 file.zir.nullTerminatedString(decl_name_index)
4359 else
4360 null;
4361
4362 // astnode
4363 const ast_node_index = idx: {
4364 const idx = self.ast_nodes.items.len;
4365 try self.ast_nodes.append(self.arena, .{
4366 .file = self.files.getIndex(file).?,
4367 .line = decl_src.line,
4368 .col = 0,
4369 .name = decl_name,
4370 .code = test_source_code,
4371 });
4372 break :idx idx;
4373 };
4374
4375 const decl_status = scope.resolveDeclName(decl_name_index, file, .none);
4376
4377 switch (decl_status.*) {
4378 .Analyzed => |idx| {
4379 self.decls.items[idx].decltest = ast_node_index;
4380 },
4381 else => unreachable, // we assume analyzeAllDecls analyzed other decls by this point
4382 }
4383}
4384
4385/// An unresolved path has a non-string WalkResult at its beginnig, while every
4386/// other element is a string WalkResult. Resolving means iteratively map each
4387/// string to a Decl / Type / Call / etc.
4388///
4389/// If we encounter an unanalyzed decl during the process, we append the
4390/// unsolved sub-path to `self.ref_paths_pending_on_decls` and bail out.
4391/// Same happens when a decl holds a type definition that hasn't been fully
4392/// analyzed yet (except that we append to `self.ref_paths_pending_on_types`.
4393///
4394/// When analyzeAllDecls / walkInstruction finishes analyzing a decl / type, it will
4395/// then check if there's any pending ref path blocked on it and, if any, it
4396/// will progress their resolution by calling tryResolveRefPath again.
4397///
4398/// Ref paths can also depend on other ref paths. See
4399/// `self.pending_ref_paths` for more info.
4400///
4401/// A ref path that has a component that resolves into a comptimeExpr will
4402/// give up its resolution process entirely, leaving the remaining components
4403/// as strings.
4404fn tryResolveRefPath(
4405 self: *Autodoc,
4406 /// File from which the decl path originates.
4407 file: *File,
4408 inst: Zir.Inst.Index, // used only for panicWithContext
4409 path: []DocData.Expr,
4410) AutodocErrors!void {
4411 var i: usize = 0;
4412 outer: while (i < path.len - 1) : (i += 1) {
4413 const parent = path[i];
4414 const child_string = path[i + 1].declName; // we expect to find an unsolved decl
4415
4416 var resolved_parent = parent;
4417 var j: usize = 0;
4418 while (j < 10_000) : (j += 1) {
4419 switch (resolved_parent) {
4420 else => break,
4421 .this => |t| resolved_parent = .{ .type = t },
4422 .declIndex => |decl_index| {
4423 const decl = self.decls.items[decl_index];
4424 resolved_parent = decl.value.expr;
4425 continue;
4426 },
4427 .declRef => |decl_status_ptr| {
4428 // NOTE: must be kep in sync with `findNameInUnsDecls`
4429 switch (decl_status_ptr.*) {
4430 // The use of unreachable here is conservative.
4431 // It might be that it truly should be up to us to
4432 // request the analys of this decl, but it's not clear
4433 // at the moment of writing.
4434 .NotRequested => unreachable,
4435 .Analyzed => |decl_index| {
4436 const decl = self.decls.items[decl_index];
4437 resolved_parent = decl.value.expr;
4438 continue;
4439 },
4440 .Pending => {
4441 // This decl path is pending completion
4442 {
4443 const res = try self.pending_ref_paths.getOrPut(
4444 self.arena,
4445 &path[path.len - 1],
4446 );
4447 if (!res.found_existing) res.value_ptr.* = .{};
4448 }
4449
4450 const res = try self.ref_paths_pending_on_decls.getOrPut(
4451 self.arena,
4452 decl_status_ptr,
4453 );
4454 if (!res.found_existing) res.value_ptr.* = .{};
4455 try res.value_ptr.*.append(self.arena, .{
4456 .file = file,
4457 .ref_path = path[i..path.len],
4458 });
4459
4460 // We return instead doing `break :outer` to prevent the
4461 // code after the :outer while loop to run, as it assumes
4462 // that the path will have been fully analyzed (or we
4463 // have given up because of a comptimeExpr).
4464 return;
4465 },
4466 }
4467 },
4468 .refPath => |rp| {
4469 if (self.pending_ref_paths.getPtr(&rp[rp.len - 1])) |waiter_list| {
4470 try waiter_list.append(self.arena, .{
4471 .file = file,
4472 .ref_path = path[i..path.len],
4473 });
4474
4475 // This decl path is pending completion
4476 {
4477 const res = try self.pending_ref_paths.getOrPut(
4478 self.arena,
4479 &path[path.len - 1],
4480 );
4481 if (!res.found_existing) res.value_ptr.* = .{};
4482 }
4483
4484 return;
4485 }
4486
4487 // If the last element is a declName or a CTE, then we give up,
4488 // otherwise we resovle the parent to it and loop again.
4489 // NOTE: we assume that if we find a string, it's because of
4490 // a CTE component somewhere in the path. We know that the path
4491 // is not pending futher evaluation because we just checked!
4492 const last = rp[rp.len - 1];
4493 switch (last) {
4494 .comptimeExpr, .declName => break :outer,
4495 else => {
4496 resolved_parent = last;
4497 continue;
4498 },
4499 }
4500 },
4501 .fieldVal => |fv| {
4502 resolved_parent = self.exprs.items[fv.val.expr];
4503 },
4504 }
4505 } else {
4506 panicWithContext(
4507 file,
4508 inst,
4509 "exhausted eval quota for `{}`in tryResolveRefPath\n",
4510 .{resolved_parent},
4511 );
4512 }
4513
4514 switch (resolved_parent) {
4515 else => {
4516 // NOTE: indirect references to types / decls should be handled
4517 // in the switch above this one!
4518 printWithContext(
4519 file,
4520 inst,
4521 "TODO: handle `{s}`in tryResolveRefPath\nInfo: {}",
4522 .{ @tagName(resolved_parent), resolved_parent },
4523 );
4524 // path[i + 1] = (try self.cteTodo("<match failure>")).expr;
4525 continue :outer;
4526 },
4527 .comptimeExpr, .call, .typeOf => {
4528 // Since we hit a cte, we leave the remaining strings unresolved
4529 // and completely give up on resolving this decl path.
4530 //decl_path.hasCte = true;
4531 break :outer;
4532 },
4533 .type => |t_index| switch (self.types.items[t_index]) {
4534 else => {
4535 panicWithContext(
4536 file,
4537 inst,
4538 "TODO: handle `{s}` in tryResolveDeclPath.type\nInfo: {}",
4539 .{ @tagName(self.types.items[t_index]), resolved_parent },
4540 );
4541 },
4542 .ComptimeExpr => {
4543 // Same as the comptimeExpr branch above
4544 break :outer;
4545 },
4546 .Unanalyzed => {
4547 // This decl path is pending completion
4548 {
4549 const res = try self.pending_ref_paths.getOrPut(
4550 self.arena,
4551 &path[path.len - 1],
4552 );
4553 if (!res.found_existing) res.value_ptr.* = .{};
4554 }
4555
4556 const res = try self.ref_paths_pending_on_types.getOrPut(
4557 self.arena,
4558 t_index,
4559 );
4560 if (!res.found_existing) res.value_ptr.* = .{};
4561 try res.value_ptr.*.append(self.arena, .{
4562 .file = file,
4563 .ref_path = path[i..path.len],
4564 });
4565
4566 return;
4567 },
4568 .Array => {
4569 if (std.mem.eql(u8, child_string, "len")) {
4570 path[i + 1] = .{
4571 .builtinField = .len,
4572 };
4573 } else {
4574 panicWithContext(
4575 file,
4576 inst,
4577 "TODO: handle `{s}` in tryResolveDeclPath.type.Array\nInfo: {}",
4578 .{ child_string, resolved_parent },
4579 );
4580 }
4581 },
4582 // TODO: the following searches could probably
4583 // be performed more efficiently on the corresponding
4584 // scope
4585 .Enum => |t_enum| { // foo.bar.baz
4586 // Look into locally-defined pub decls
4587 for (t_enum.pubDecls) |idx| {
4588 const d = self.decls.items[idx];
4589 if (d.is_uns) continue;
4590 if (std.mem.eql(u8, d.name, child_string)) {
4591 path[i + 1] = .{ .declIndex = idx };
4592 continue :outer;
4593 }
4594 }
4595
4596 // Look into locally-defined priv decls
4597 for (t_enum.privDecls) |idx| {
4598 const d = self.decls.items[idx];
4599 if (d.is_uns) continue;
4600 if (std.mem.eql(u8, d.name, child_string)) {
4601 path[i + 1] = .{ .declIndex = idx };
4602 continue :outer;
4603 }
4604 }
4605
4606 switch (try self.findNameInUnsDecls(file, path[i..path.len], resolved_parent, child_string)) {
4607 .Pending => return,
4608 .NotFound => {},
4609 .Found => |match| {
4610 path[i + 1] = match;
4611 continue :outer;
4612 },
4613 }
4614
4615 for (self.ast_nodes.items[t_enum.src].fields.?, 0..) |ast_node, idx| {
4616 const name = self.ast_nodes.items[ast_node].name.?;
4617 if (std.mem.eql(u8, name, child_string)) {
4618 // TODO: should we really create an artificial
4619 // decl for this type? Probably not.
4620
4621 path[i + 1] = .{
4622 .fieldRef = .{
4623 .type = t_index,
4624 .index = idx,
4625 },
4626 };
4627 continue :outer;
4628 }
4629 }
4630
4631 // if we got here, our search failed
4632 printWithContext(
4633 file,
4634 inst,
4635 "failed to match `{s}` in enum",
4636 .{child_string},
4637 );
4638
4639 path[i + 1] = (try self.cteTodo("match failure")).expr;
4640 continue :outer;
4641 },
4642 .Union => |t_union| {
4643 // Look into locally-defined pub decls
4644 for (t_union.pubDecls) |idx| {
4645 const d = self.decls.items[idx];
4646 if (d.is_uns) continue;
4647 if (std.mem.eql(u8, d.name, child_string)) {
4648 path[i + 1] = .{ .declIndex = idx };
4649 continue :outer;
4650 }
4651 }
4652
4653 // Look into locally-defined priv decls
4654 for (t_union.privDecls) |idx| {
4655 const d = self.decls.items[idx];
4656 if (d.is_uns) continue;
4657 if (std.mem.eql(u8, d.name, child_string)) {
4658 path[i + 1] = .{ .declIndex = idx };
4659 continue :outer;
4660 }
4661 }
4662
4663 switch (try self.findNameInUnsDecls(file, path[i..path.len], resolved_parent, child_string)) {
4664 .Pending => return,
4665 .NotFound => {},
4666 .Found => |match| {
4667 path[i + 1] = match;
4668 continue :outer;
4669 },
4670 }
4671
4672 for (self.ast_nodes.items[t_union.src].fields.?, 0..) |ast_node, idx| {
4673 const name = self.ast_nodes.items[ast_node].name.?;
4674 if (std.mem.eql(u8, name, child_string)) {
4675 // TODO: should we really create an artificial
4676 // decl for this type? Probably not.
4677
4678 path[i + 1] = .{
4679 .fieldRef = .{
4680 .type = t_index,
4681 .index = idx,
4682 },
4683 };
4684 continue :outer;
4685 }
4686 }
4687
4688 // if we got here, our search failed
4689 printWithContext(
4690 file,
4691 inst,
4692 "failed to match `{s}` in union",
4693 .{child_string},
4694 );
4695 path[i + 1] = (try self.cteTodo("match failure")).expr;
4696 continue :outer;
4697 },
4698
4699 .Struct => |t_struct| {
4700 // Look into locally-defined pub decls
4701 for (t_struct.pubDecls) |idx| {
4702 const d = self.decls.items[idx];
4703 if (d.is_uns) continue;
4704 if (std.mem.eql(u8, d.name, child_string)) {
4705 path[i + 1] = .{ .declIndex = idx };
4706 continue :outer;
4707 }
4708 }
4709
4710 // Look into locally-defined priv decls
4711 for (t_struct.privDecls) |idx| {
4712 const d = self.decls.items[idx];
4713 if (d.is_uns) continue;
4714 if (std.mem.eql(u8, d.name, child_string)) {
4715 path[i + 1] = .{ .declIndex = idx };
4716 continue :outer;
4717 }
4718 }
4719
4720 switch (try self.findNameInUnsDecls(file, path[i..path.len], resolved_parent, child_string)) {
4721 .Pending => return,
4722 .NotFound => {},
4723 .Found => |match| {
4724 path[i + 1] = match;
4725 continue :outer;
4726 },
4727 }
4728
4729 for (self.ast_nodes.items[t_struct.src].fields.?, 0..) |ast_node, idx| {
4730 const name = self.ast_nodes.items[ast_node].name.?;
4731 if (std.mem.eql(u8, name, child_string)) {
4732 // TODO: should we really create an artificial
4733 // decl for this type? Probably not.
4734
4735 path[i + 1] = .{
4736 .fieldRef = .{
4737 .type = t_index,
4738 .index = idx,
4739 },
4740 };
4741 continue :outer;
4742 }
4743 }
4744
4745 // if we got here, our search failed
4746 // printWithContext(
4747 // file,
4748 // inst,
4749 // "failed to match `{s}` in struct",
4750 // .{child_string},
4751 // );
4752 // path[i + 1] = (try self.cteTodo("match failure")).expr;
4753 //
4754 // that's working
4755 path[i + 1] = (try self.cteTodo(child_string)).expr;
4756 continue :outer;
4757 },
4758 .Opaque => |t_opaque| {
4759 // Look into locally-defined pub decls
4760 for (t_opaque.pubDecls) |idx| {
4761 const d = self.decls.items[idx];
4762 if (d.is_uns) continue;
4763 if (std.mem.eql(u8, d.name, child_string)) {
4764 path[i + 1] = .{ .declIndex = idx };
4765 continue :outer;
4766 }
4767 }
4768
4769 // Look into locally-defined priv decls
4770 for (t_opaque.privDecls) |idx| {
4771 const d = self.decls.items[idx];
4772 if (d.is_uns) continue;
4773 if (std.mem.eql(u8, d.name, child_string)) {
4774 path[i + 1] = .{ .declIndex = idx };
4775 continue :outer;
4776 }
4777 }
4778
4779 // We delay looking into Uns decls since they could be
4780 // not fully analyzed yet.
4781 switch (try self.findNameInUnsDecls(file, path[i..path.len], resolved_parent, child_string)) {
4782 .Pending => return,
4783 .NotFound => {},
4784 .Found => |match| {
4785 path[i + 1] = match;
4786 continue :outer;
4787 },
4788 }
4789
4790 // if we got here, our search failed
4791 printWithContext(
4792 file,
4793 inst,
4794 "failed to match `{s}` in opaque",
4795 .{child_string},
4796 );
4797
4798 path[i + 1] = (try self.cteTodo("match failure")).expr;
4799 continue :outer;
4800 },
4801 },
4802 .@"struct" => |st| {
4803 for (st) |field| {
4804 if (std.mem.eql(u8, field.name, child_string)) {
4805 path[i + 1] = .{ .fieldVal = field };
4806 continue :outer;
4807 }
4808 }
4809
4810 // if we got here, our search failed
4811 printWithContext(
4812 file,
4813 inst,
4814 "failed to match `{s}` in struct",
4815 .{child_string},
4816 );
4817
4818 path[i + 1] = (try self.cteTodo("match failure")).expr;
4819 continue :outer;
4820 },
4821 }
4822 }
4823
4824 if (self.pending_ref_paths.get(&path[path.len - 1])) |waiter_list| {
4825 // It's important to de-register ourselves as pending before
4826 // attempting to resolve any other decl.
4827 _ = self.pending_ref_paths.remove(&path[path.len - 1]);
4828
4829 for (waiter_list.items) |resume_info| {
4830 try self.tryResolveRefPath(resume_info.file, inst, resume_info.ref_path);
4831 }
4832 // TODO: this is where we should free waiter_list, but its in the arena
4833 // that said, we might want to store it elsewhere and reclaim memory asap
4834 }
4835}
4836
4837const UnsSearchResult = union(enum) {
4838 Found: DocData.Expr,
4839 Pending,
4840 NotFound,
4841};
4842
4843fn findNameInUnsDecls(
4844 self: *Autodoc,
4845 file: *File,
4846 tail: []DocData.Expr,
4847 uns_expr: DocData.Expr,
4848 name: []const u8,
4849) !UnsSearchResult {
4850 var to_analyze = std.SegmentedList(DocData.Expr, 1){};
4851 // TODO: make this an appendAssumeCapacity
4852 try to_analyze.append(self.arena, uns_expr);
4853
4854 while (to_analyze.pop()) |cte| {
4855 var container_expression = cte;
4856 for (0..10_000) |_| {
4857 // TODO: handle other types of indirection, like @import
4858 const type_index = switch (container_expression) {
4859 .type => |t| t,
4860 .declRef => |decl_status_ptr| {
4861 switch (decl_status_ptr.*) {
4862 // The use of unreachable here is conservative.
4863 // It might be that it truly should be up to us to
4864 // request the analys of this decl, but it's not clear
4865 // at the moment of writing.
4866 .NotRequested => unreachable,
4867 .Analyzed => |decl_index| {
4868 const decl = self.decls.items[decl_index];
4869 container_expression = decl.value.expr;
4870 continue;
4871 },
4872 .Pending => {
4873 // This decl path is pending completion
4874 {
4875 const res = try self.pending_ref_paths.getOrPut(
4876 self.arena,
4877 &tail[tail.len - 1],
4878 );
4879 if (!res.found_existing) res.value_ptr.* = .{};
4880 }
4881
4882 const res = try self.ref_paths_pending_on_decls.getOrPut(
4883 self.arena,
4884 decl_status_ptr,
4885 );
4886 if (!res.found_existing) res.value_ptr.* = .{};
4887 try res.value_ptr.*.append(self.arena, .{
4888 .file = file,
4889 .ref_path = tail,
4890 });
4891
4892 // TODO: save some state that keeps track of our
4893 // progress because, as things stand, we
4894 // always re-start the search from scratch
4895 return .Pending;
4896 },
4897 }
4898 },
4899 else => {
4900 log.debug(
4901 "Handle `{s}` in findNameInUnsDecls (first switch)",
4902 .{@tagName(cte)},
4903 );
4904 return .{ .Found = .{ .comptimeExpr = 0 } };
4905 },
4906 };
4907
4908 const t = self.types.items[type_index];
4909 const decls = switch (t) {
4910 else => {
4911 log.debug(
4912 "Handle `{s}` in findNameInUnsDecls (second switch)",
4913 .{@tagName(cte)},
4914 );
4915 return .{ .Found = .{ .comptimeExpr = 0 } };
4916 },
4917 inline .Struct, .Union, .Opaque, .Enum => |c| c.pubDecls,
4918 };
4919
4920 for (decls) |idx| {
4921 const d = self.decls.items[idx];
4922 if (d.is_uns) {
4923 try to_analyze.append(self.arena, d.value.expr);
4924 } else if (std.mem.eql(u8, d.name, name)) {
4925 return .{ .Found = .{ .declIndex = idx } };
4926 }
4927 }
4928 }
4929 }
4930
4931 return .NotFound;
4932}
4933
4934fn analyzeFancyFunction(
4935 self: *Autodoc,
4936 file: *File,
4937 scope: *Scope,
4938 parent_src: SrcLocInfo,
4939 inst: Zir.Inst.Index,
4940 self_ast_node_index: usize,
4941 type_slot_index: usize,
4942 call_ctx: ?*const CallContext,
4943) AutodocErrors!DocData.WalkResult {
4944 const tags = file.zir.instructions.items(.tag);
4945 const data = file.zir.instructions.items(.data);
4946 const fn_info = file.zir.getFnInfo(inst);
4947
4948 try self.ast_nodes.ensureUnusedCapacity(self.arena, fn_info.total_params_len);
4949 var param_type_refs = try std.ArrayListUnmanaged(DocData.Expr).initCapacity(
4950 self.arena,
4951 fn_info.total_params_len,
4952 );
4953 var param_ast_indexes = try std.ArrayListUnmanaged(usize).initCapacity(
4954 self.arena,
4955 fn_info.total_params_len,
4956 );
4957
4958 // TODO: handle scope rules for fn parameters
4959 for (fn_info.param_body[0..fn_info.total_params_len]) |param_index| {
4960 switch (tags[@intFromEnum(param_index)]) {
4961 else => {
4962 panicWithContext(
4963 file,
4964 param_index,
4965 "TODO: handle `{s}` in walkInstruction.func\n",
4966 .{@tagName(tags[@intFromEnum(param_index)])},
4967 );
4968 },
4969 .param_anytype, .param_anytype_comptime => {
4970 // TODO: where are the doc comments?
4971 const str_tok = data[@intFromEnum(param_index)].str_tok;
4972
4973 const name = str_tok.get(file.zir);
4974
4975 param_ast_indexes.appendAssumeCapacity(self.ast_nodes.items.len);
4976 self.ast_nodes.appendAssumeCapacity(.{
4977 .name = name,
4978 .docs = "",
4979 .@"comptime" = tags[@intFromEnum(param_index)] == .param_anytype_comptime,
4980 });
4981
4982 param_type_refs.appendAssumeCapacity(
4983 DocData.Expr{ .@"anytype" = .{} },
4984 );
4985 },
4986 .param, .param_comptime => {
4987 const pl_tok = data[@intFromEnum(param_index)].pl_tok;
4988 const extra = file.zir.extraData(Zir.Inst.Param, pl_tok.payload_index);
4989 const doc_comment = if (extra.data.doc_comment != .empty)
4990 file.zir.nullTerminatedString(extra.data.doc_comment)
4991 else
4992 "";
4993 const name = file.zir.nullTerminatedString(extra.data.name);
4994
4995 param_ast_indexes.appendAssumeCapacity(self.ast_nodes.items.len);
4996 try self.ast_nodes.append(self.arena, .{
4997 .name = name,
4998 .docs = doc_comment,
4999 .@"comptime" = tags[@intFromEnum(param_index)] == .param_comptime,
5000 });
5001
5002 const break_index = file.zir.extra[extra.end..][extra.data.body_len - 1];
5003 const break_operand = data[break_index].@"break".operand;
5004 const param_type_ref = try self.walkRef(
5005 file,
5006 scope,
5007 parent_src,
5008 break_operand,
5009 false,
5010 call_ctx,
5011 );
5012
5013 param_type_refs.appendAssumeCapacity(param_type_ref.expr);
5014 },
5015 }
5016 }
5017
5018 self.ast_nodes.items[self_ast_node_index].fields = param_ast_indexes.items;
5019
5020 const pl_node = data[@intFromEnum(inst)].pl_node;
5021 const extra = file.zir.extraData(Zir.Inst.FuncFancy, pl_node.payload_index);
5022
5023 var extra_index: usize = extra.end;
5024
5025 var lib_name: []const u8 = "";
5026 if (extra.data.bits.has_lib_name) {
5027 const lib_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
5028 lib_name = file.zir.nullTerminatedString(lib_name_index);
5029 extra_index += 1;
5030 }
5031
5032 var align_index: ?usize = null;
5033 if (extra.data.bits.has_align_ref) {
5034 const align_ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
5035 align_index = self.exprs.items.len;
5036 _ = try self.walkRef(
5037 file,
5038 scope,
5039 parent_src,
5040 align_ref,
5041 false,
5042 call_ctx,
5043 );
5044 extra_index += 1;
5045 } else if (extra.data.bits.has_align_body) {
5046 const align_body_len = file.zir.extra[extra_index];
5047 extra_index += 1;
5048 const align_body = file.zir.extra[extra_index .. extra_index + align_body_len];
5049 _ = align_body;
5050 // TODO: analyze the block (or bail with a comptimeExpr)
5051 extra_index += align_body_len;
5052 } else {
5053 // default alignment
5054 }
5055
5056 var addrspace_index: ?usize = null;
5057 if (extra.data.bits.has_addrspace_ref) {
5058 const addrspace_ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
5059 addrspace_index = self.exprs.items.len;
5060 _ = try self.walkRef(
5061 file,
5062 scope,
5063 parent_src,
5064 addrspace_ref,
5065 false,
5066 call_ctx,
5067 );
5068 extra_index += 1;
5069 } else if (extra.data.bits.has_addrspace_body) {
5070 const addrspace_body_len = file.zir.extra[extra_index];
5071 extra_index += 1;
5072 const addrspace_body = file.zir.extra[extra_index .. extra_index + addrspace_body_len];
5073 _ = addrspace_body;
5074 // TODO: analyze the block (or bail with a comptimeExpr)
5075 extra_index += addrspace_body_len;
5076 } else {
5077 // default alignment
5078 }
5079
5080 var section_index: ?usize = null;
5081 if (extra.data.bits.has_section_ref) {
5082 const section_ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
5083 section_index = self.exprs.items.len;
5084 _ = try self.walkRef(
5085 file,
5086 scope,
5087 parent_src,
5088 section_ref,
5089 false,
5090 call_ctx,
5091 );
5092 extra_index += 1;
5093 } else if (extra.data.bits.has_section_body) {
5094 const section_body_len = file.zir.extra[extra_index];
5095 extra_index += 1;
5096 const section_body = file.zir.extra[extra_index .. extra_index + section_body_len];
5097 _ = section_body;
5098 // TODO: analyze the block (or bail with a comptimeExpr)
5099 extra_index += section_body_len;
5100 } else {
5101 // default alignment
5102 }
5103
5104 var cc_index: ?usize = null;
5105 if (extra.data.bits.has_cc_ref and !extra.data.bits.has_cc_body) {
5106 const cc_ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
5107 const cc_expr = try self.walkRef(
5108 file,
5109 scope,
5110 parent_src,
5111 cc_ref,
5112 false,
5113 call_ctx,
5114 );
5115
5116 cc_index = self.exprs.items.len;
5117 try self.exprs.append(self.arena, cc_expr.expr);
5118
5119 extra_index += 1;
5120 } else if (extra.data.bits.has_cc_body) {
5121 const cc_body_len = file.zir.extra[extra_index];
5122 extra_index += 1;
5123 const cc_body = file.zir.bodySlice(extra_index, cc_body_len);
5124
5125 // We assume the body ends with a break_inline
5126 const break_index = cc_body[cc_body.len - 1];
5127 const break_operand = data[@intFromEnum(break_index)].@"break".operand;
5128 const cc_expr = try self.walkRef(
5129 file,
5130 scope,
5131 parent_src,
5132 break_operand,
5133 false,
5134 call_ctx,
5135 );
5136
5137 cc_index = self.exprs.items.len;
5138 try self.exprs.append(self.arena, cc_expr.expr);
5139
5140 extra_index += cc_body_len;
5141 } else {
5142 // auto calling convention
5143 }
5144
5145 // ret
5146 const ret_type_ref: DocData.Expr = switch (fn_info.ret_ty_body.len) {
5147 0 => switch (fn_info.ret_ty_ref) {
5148 .none => DocData.Expr{ .void = .{} },
5149 else => blk: {
5150 const ref = fn_info.ret_ty_ref;
5151 const wr = try self.walkRef(
5152 file,
5153 scope,
5154 parent_src,
5155 ref,
5156 false,
5157 call_ctx,
5158 );
5159 break :blk wr.expr;
5160 },
5161 },
5162 else => blk: {
5163 const last_instr_index = fn_info.ret_ty_body[fn_info.ret_ty_body.len - 1];
5164 const break_operand = data[@intFromEnum(last_instr_index)].@"break".operand;
5165 const wr = try self.walkRef(
5166 file,
5167 scope,
5168 parent_src,
5169 break_operand,
5170 false,
5171 call_ctx,
5172 );
5173 break :blk wr.expr;
5174 },
5175 };
5176
5177 // TODO: a complete version of this will probably need a scope
5178 // in order to evaluate correctly closures around funcion
5179 // parameters etc.
5180 const generic_ret: ?DocData.Expr = switch (ret_type_ref) {
5181 .type => |t| blk: {
5182 if (fn_info.body.len == 0) break :blk null;
5183 if (t == @intFromEnum(Ref.type_type)) {
5184 break :blk try self.getGenericReturnType(
5185 file,
5186 scope,
5187 parent_src,
5188 fn_info.body,
5189 call_ctx,
5190 );
5191 } else {
5192 break :blk null;
5193 }
5194 },
5195 else => null,
5196 };
5197
5198 // if we're analyzing a function signature (ie without body), we
5199 // actually don't have an ast_node reserved for us, but since
5200 // we don't have a name, we don't need it.
5201 const src = if (fn_info.body.len == 0) 0 else self_ast_node_index;
5202
5203 self.types.items[type_slot_index] = .{
5204 .Fn = .{
5205 .name = "todo_name func",
5206 .src = src,
5207 .params = param_type_refs.items,
5208 .ret = ret_type_ref,
5209 .generic_ret = generic_ret,
5210 .is_extern = extra.data.bits.is_extern,
5211 .has_cc = cc_index != null,
5212 .has_align = align_index != null,
5213 .has_lib_name = extra.data.bits.has_lib_name,
5214 .lib_name = lib_name,
5215 .is_inferred_error = extra.data.bits.is_inferred_error,
5216 .cc = cc_index,
5217 .@"align" = align_index,
5218 },
5219 };
5220
5221 return DocData.WalkResult{
5222 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
5223 .expr = .{ .type = type_slot_index },
5224 };
5225}
5226fn analyzeFunction(
5227 self: *Autodoc,
5228 file: *File,
5229 scope: *Scope,
5230 parent_src: SrcLocInfo,
5231 inst: Zir.Inst.Index,
5232 self_ast_node_index: usize,
5233 type_slot_index: usize,
5234 ret_is_inferred_error_set: bool,
5235 call_ctx: ?*const CallContext,
5236) AutodocErrors!DocData.WalkResult {
5237 const tags = file.zir.instructions.items(.tag);
5238 const data = file.zir.instructions.items(.data);
5239 const fn_info = file.zir.getFnInfo(inst);
5240
5241 try self.ast_nodes.ensureUnusedCapacity(self.arena, fn_info.total_params_len);
5242 var param_type_refs = try std.ArrayListUnmanaged(DocData.Expr).initCapacity(
5243 self.arena,
5244 fn_info.total_params_len,
5245 );
5246 var param_ast_indexes = try std.ArrayListUnmanaged(usize).initCapacity(
5247 self.arena,
5248 fn_info.total_params_len,
5249 );
5250
5251 // TODO: handle scope rules for fn parameters
5252 for (fn_info.param_body[0..fn_info.total_params_len]) |param_index| {
5253 switch (tags[@intFromEnum(param_index)]) {
5254 else => {
5255 panicWithContext(
5256 file,
5257 param_index,
5258 "TODO: handle `{s}` in walkInstruction.func\n",
5259 .{@tagName(tags[@intFromEnum(param_index)])},
5260 );
5261 },
5262 .param_anytype, .param_anytype_comptime => {
5263 // TODO: where are the doc comments?
5264 const str_tok = data[@intFromEnum(param_index)].str_tok;
5265
5266 const name = str_tok.get(file.zir);
5267
5268 param_ast_indexes.appendAssumeCapacity(self.ast_nodes.items.len);
5269 self.ast_nodes.appendAssumeCapacity(.{
5270 .name = name,
5271 .docs = "",
5272 .@"comptime" = tags[@intFromEnum(param_index)] == .param_anytype_comptime,
5273 });
5274
5275 param_type_refs.appendAssumeCapacity(
5276 DocData.Expr{ .@"anytype" = .{} },
5277 );
5278 },
5279 .param, .param_comptime => {
5280 const pl_tok = data[@intFromEnum(param_index)].pl_tok;
5281 const extra = file.zir.extraData(Zir.Inst.Param, pl_tok.payload_index);
5282 const doc_comment = if (extra.data.doc_comment != .empty)
5283 file.zir.nullTerminatedString(extra.data.doc_comment)
5284 else
5285 "";
5286 const name = file.zir.nullTerminatedString(extra.data.name);
5287
5288 param_ast_indexes.appendAssumeCapacity(self.ast_nodes.items.len);
5289 try self.ast_nodes.append(self.arena, .{
5290 .name = name,
5291 .docs = doc_comment,
5292 .@"comptime" = tags[@intFromEnum(param_index)] == .param_comptime,
5293 });
5294
5295 const break_index = file.zir.extra[extra.end..][extra.data.body_len - 1];
5296 const break_operand = data[break_index].@"break".operand;
5297 const param_type_ref = try self.walkRef(
5298 file,
5299 scope,
5300 parent_src,
5301 break_operand,
5302 false,
5303 call_ctx,
5304 );
5305
5306 param_type_refs.appendAssumeCapacity(param_type_ref.expr);
5307 },
5308 }
5309 }
5310
5311 // ret
5312 const ret_type_ref: DocData.Expr = switch (fn_info.ret_ty_body.len) {
5313 0 => switch (fn_info.ret_ty_ref) {
5314 .none => DocData.Expr{ .void = .{} },
5315 else => blk: {
5316 const ref = fn_info.ret_ty_ref;
5317 const wr = try self.walkRef(
5318 file,
5319 scope,
5320 parent_src,
5321 ref,
5322 false,
5323 call_ctx,
5324 );
5325 break :blk wr.expr;
5326 },
5327 },
5328 else => blk: {
5329 const last_instr_index = fn_info.ret_ty_body[fn_info.ret_ty_body.len - 1];
5330 const break_operand = data[@intFromEnum(last_instr_index)].@"break".operand;
5331 const wr = try self.walkRef(
5332 file,
5333 scope,
5334 parent_src,
5335 break_operand,
5336 false,
5337 call_ctx,
5338 );
5339 break :blk wr.expr;
5340 },
5341 };
5342
5343 // TODO: a complete version of this will probably need a scope
5344 // in order to evaluate correctly closures around funcion
5345 // parameters etc.
5346 const generic_ret: ?DocData.Expr = switch (ret_type_ref) {
5347 .type => |t| blk: {
5348 if (fn_info.body.len == 0) break :blk null;
5349 if (t == @intFromEnum(Ref.type_type)) {
5350 break :blk try self.getGenericReturnType(
5351 file,
5352 scope,
5353 parent_src,
5354 fn_info.body,
5355 call_ctx,
5356 );
5357 } else {
5358 break :blk null;
5359 }
5360 },
5361 else => null,
5362 };
5363
5364 const ret_type: DocData.Expr = blk: {
5365 if (ret_is_inferred_error_set) {
5366 const ret_type_slot_index = self.types.items.len;
5367 try self.types.append(self.arena, .{
5368 .InferredErrorUnion = .{ .payload = ret_type_ref },
5369 });
5370 break :blk .{ .type = ret_type_slot_index };
5371 } else break :blk ret_type_ref;
5372 };
5373
5374 // if we're analyzing a function signature (ie without body), we
5375 // actually don't have an ast_node reserved for us, but since
5376 // we don't have a name, we don't need it.
5377 const src = if (fn_info.body.len == 0) 0 else self_ast_node_index;
5378
5379 self.ast_nodes.items[self_ast_node_index].fields = param_ast_indexes.items;
5380 self.types.items[type_slot_index] = .{
5381 .Fn = .{
5382 .name = "todo_name func",
5383 .src = src,
5384 .params = param_type_refs.items,
5385 .ret = ret_type,
5386 .generic_ret = generic_ret,
5387 },
5388 };
5389
5390 return DocData.WalkResult{
5391 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
5392 .expr = .{ .type = type_slot_index },
5393 };
5394}
5395
5396fn getGenericReturnType(
5397 self: *Autodoc,
5398 file: *File,
5399 scope: *Scope,
5400 parent_src: SrcLocInfo, // function decl line
5401 body: []const Zir.Inst.Index,
5402 call_ctx: ?*const CallContext,
5403) !DocData.Expr {
5404 const tags = file.zir.instructions.items(.tag);
5405 if (body.len >= 4) {
5406 const maybe_ret_inst = body[body.len - 4];
5407 switch (tags[@intFromEnum(maybe_ret_inst)]) {
5408 .ret_node, .ret_load => {
5409 const wr = try self.walkInstruction(
5410 file,
5411 scope,
5412 parent_src,
5413 maybe_ret_inst,
5414 false,
5415 call_ctx,
5416 );
5417 return wr.expr;
5418 },
5419 else => {},
5420 }
5421 }
5422 return DocData.Expr{ .comptimeExpr = 0 };
5423}
5424
5425fn collectUnionFieldInfo(
5426 self: *Autodoc,
5427 file: *File,
5428 scope: *Scope,
5429 parent_src: SrcLocInfo,
5430 fields_len: usize,
5431 field_type_refs: *std.ArrayListUnmanaged(DocData.Expr),
5432 field_name_indexes: *std.ArrayListUnmanaged(usize),
5433 ei: usize,
5434 call_ctx: ?*const CallContext,
5435) !void {
5436 if (fields_len == 0) return;
5437 var extra_index = ei;
5438
5439 const bits_per_field = 4;
5440 const fields_per_u32 = 32 / bits_per_field;
5441 const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
5442 var bit_bag_index: usize = extra_index;
5443 extra_index += bit_bags_count;
5444
5445 var cur_bit_bag: u32 = undefined;
5446 var field_i: u32 = 0;
5447 while (field_i < fields_len) : (field_i += 1) {
5448 if (field_i % fields_per_u32 == 0) {
5449 cur_bit_bag = file.zir.extra[bit_bag_index];
5450 bit_bag_index += 1;
5451 }
5452 const has_type = @as(u1, @truncate(cur_bit_bag)) != 0;
5453 cur_bit_bag >>= 1;
5454 const has_align = @as(u1, @truncate(cur_bit_bag)) != 0;
5455 cur_bit_bag >>= 1;
5456 const has_tag = @as(u1, @truncate(cur_bit_bag)) != 0;
5457 cur_bit_bag >>= 1;
5458 const unused = @as(u1, @truncate(cur_bit_bag)) != 0;
5459 cur_bit_bag >>= 1;
5460 _ = unused;
5461
5462 const field_name = file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[extra_index]));
5463 extra_index += 1;
5464 const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
5465 extra_index += 1;
5466 const field_type: Zir.Inst.Ref = if (has_type) @enumFromInt(file.zir.extra[extra_index]) else .void_type;
5467 if (has_type) extra_index += 1;
5468
5469 if (has_align) extra_index += 1;
5470 if (has_tag) extra_index += 1;
5471
5472 // type
5473 {
5474 const walk_result = try self.walkRef(
5475 file,
5476 scope,
5477 parent_src,
5478 field_type,
5479 false,
5480 call_ctx,
5481 );
5482 try field_type_refs.append(self.arena, walk_result.expr);
5483 }
5484
5485 // ast node
5486 {
5487 try field_name_indexes.append(self.arena, self.ast_nodes.items.len);
5488 const doc_comment: ?[]const u8 = if (doc_comment_index != .empty)
5489 file.zir.nullTerminatedString(doc_comment_index)
5490 else
5491 null;
5492 try self.ast_nodes.append(self.arena, .{
5493 .name = field_name,
5494 .docs = doc_comment,
5495 });
5496 }
5497 }
5498}
5499
5500fn collectStructFieldInfo(
5501 self: *Autodoc,
5502 file: *File,
5503 scope: *Scope,
5504 parent_src: SrcLocInfo,
5505 fields_len: usize,
5506 field_type_refs: *std.ArrayListUnmanaged(DocData.Expr),
5507 field_default_refs: *std.ArrayListUnmanaged(?DocData.Expr),
5508 field_name_indexes: *std.ArrayListUnmanaged(usize),
5509 ei: usize,
5510 is_tuple: bool,
5511 call_ctx: ?*const CallContext,
5512) !void {
5513 if (fields_len == 0) return;
5514 var extra_index = ei;
5515
5516 const bits_per_field = 4;
5517 const fields_per_u32 = 32 / bits_per_field;
5518 const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
5519
5520 const Field = struct {
5521 field_name: Zir.NullTerminatedString,
5522 doc_comment_index: Zir.NullTerminatedString,
5523 type_body_len: u32 = 0,
5524 align_body_len: u32 = 0,
5525 init_body_len: u32 = 0,
5526 type_ref: Zir.Inst.Ref = .none,
5527 };
5528 const fields = try self.arena.alloc(Field, fields_len);
5529
5530 var bit_bag_index: usize = extra_index;
5531 extra_index += bit_bags_count;
5532
5533 var cur_bit_bag: u32 = undefined;
5534 var field_i: u32 = 0;
5535 while (field_i < fields_len) : (field_i += 1) {
5536 if (field_i % fields_per_u32 == 0) {
5537 cur_bit_bag = file.zir.extra[bit_bag_index];
5538 bit_bag_index += 1;
5539 }
5540 const has_align = @as(u1, @truncate(cur_bit_bag)) != 0;
5541 cur_bit_bag >>= 1;
5542 const has_default = @as(u1, @truncate(cur_bit_bag)) != 0;
5543 cur_bit_bag >>= 1;
5544 // const is_comptime = @truncate(u1, cur_bit_bag) != 0;
5545 cur_bit_bag >>= 1;
5546 const has_type_body = @as(u1, @truncate(cur_bit_bag)) != 0;
5547 cur_bit_bag >>= 1;
5548
5549 const field_name: Zir.NullTerminatedString = if (!is_tuple) blk: {
5550 const fname = file.zir.extra[extra_index];
5551 extra_index += 1;
5552 break :blk @enumFromInt(fname);
5553 } else .empty;
5554
5555 const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
5556 extra_index += 1;
5557
5558 fields[field_i] = .{
5559 .field_name = field_name,
5560 .doc_comment_index = doc_comment_index,
5561 };
5562
5563 if (has_type_body) {
5564 fields[field_i].type_body_len = file.zir.extra[extra_index];
5565 } else {
5566 fields[field_i].type_ref = @enumFromInt(file.zir.extra[extra_index]);
5567 }
5568 extra_index += 1;
5569
5570 if (has_align) {
5571 fields[field_i].align_body_len = file.zir.extra[extra_index];
5572 extra_index += 1;
5573 }
5574 if (has_default) {
5575 fields[field_i].init_body_len = file.zir.extra[extra_index];
5576 extra_index += 1;
5577 }
5578 }
5579
5580 const data = file.zir.instructions.items(.data);
5581
5582 for (fields) |field| {
5583 const type_expr = expr: {
5584 if (field.type_ref != .none) {
5585 const walk_result = try self.walkRef(
5586 file,
5587 scope,
5588 parent_src,
5589 field.type_ref,
5590 false,
5591 call_ctx,
5592 );
5593 break :expr walk_result.expr;
5594 }
5595
5596 std.debug.assert(field.type_body_len != 0);
5597 const body = file.zir.bodySlice(extra_index, field.type_body_len);
5598 extra_index += body.len;
5599
5600 const break_inst = body[body.len - 1];
5601 const operand = data[@intFromEnum(break_inst)].@"break".operand;
5602 try self.ast_nodes.append(self.arena, .{
5603 .file = self.files.getIndex(file).?,
5604 .line = parent_src.line,
5605 .col = 0,
5606 .fields = null, // walkInstruction will fill `fields` if necessary
5607 });
5608 const walk_result = try self.walkRef(
5609 file,
5610 scope,
5611 parent_src,
5612 operand,
5613 false,
5614 call_ctx,
5615 );
5616 break :expr walk_result.expr;
5617 };
5618
5619 extra_index += field.align_body_len;
5620
5621 const default_expr: ?DocData.Expr = def: {
5622 if (field.init_body_len == 0) {
5623 break :def null;
5624 }
5625
5626 const body = file.zir.bodySlice(extra_index, field.init_body_len);
5627 extra_index += body.len;
5628
5629 const break_inst = body[body.len - 1];
5630 const operand = data[@intFromEnum(break_inst)].@"break".operand;
5631 const walk_result = try self.walkRef(
5632 file,
5633 scope,
5634 parent_src,
5635 operand,
5636 false,
5637 call_ctx,
5638 );
5639 break :def walk_result.expr;
5640 };
5641
5642 try field_type_refs.append(self.arena, type_expr);
5643 try field_default_refs.append(self.arena, default_expr);
5644
5645 // ast node
5646 {
5647 try field_name_indexes.append(self.arena, self.ast_nodes.items.len);
5648 const doc_comment: ?[]const u8 = if (field.doc_comment_index != .empty)
5649 file.zir.nullTerminatedString(field.doc_comment_index)
5650 else
5651 null;
5652 const field_name: []const u8 = if (field.field_name != .empty)
5653 file.zir.nullTerminatedString(field.field_name)
5654 else
5655 "";
5656
5657 try self.ast_nodes.append(self.arena, .{
5658 .name = field_name,
5659 .docs = doc_comment,
5660 });
5661 }
5662 }
5663}
5664
5665/// A Zir Ref can either refer to common types and values, or to a Zir index.
5666/// WalkRef resolves common cases and delegates to `walkInstruction` otherwise.
5667fn walkRef(
5668 self: *Autodoc,
5669 file: *File,
5670 parent_scope: *Scope,
5671 parent_src: SrcLocInfo,
5672 ref: Ref,
5673 need_type: bool, // true when the caller needs also a typeRef for the return value
5674 call_ctx: ?*const CallContext,
5675) AutodocErrors!DocData.WalkResult {
5676 if (ref == .none) {
5677 return .{ .expr = .{ .comptimeExpr = 0 } };
5678 } else if (@intFromEnum(ref) <= @intFromEnum(InternPool.Index.last_type)) {
5679 // We can just return a type that indexes into `types` with the
5680 // enum value because in the beginning we pre-filled `types` with
5681 // the types that are listed in `Ref`.
5682 return DocData.WalkResult{
5683 .typeRef = .{ .type = @intFromEnum(std.builtin.TypeId.Type) },
5684 .expr = .{ .type = @intFromEnum(ref) },
5685 };
5686 } else if (ref.toIndex()) |zir_index| {
5687 return self.walkInstruction(
5688 file,
5689 parent_scope,
5690 parent_src,
5691 zir_index,
5692 need_type,
5693 call_ctx,
5694 );
5695 } else {
5696 switch (ref) {
5697 else => {
5698 panicWithOptionalContext(
5699 file,
5700 .none,
5701 "TODO: handle {s} in walkRef",
5702 .{@tagName(ref)},
5703 );
5704 },
5705 .undef => {
5706 return DocData.WalkResult{ .expr = .undefined };
5707 },
5708 .zero => {
5709 return DocData.WalkResult{
5710 .typeRef = .{ .type = @intFromEnum(Ref.comptime_int_type) },
5711 .expr = .{ .int = .{ .value = 0 } },
5712 };
5713 },
5714 .one => {
5715 return DocData.WalkResult{
5716 .typeRef = .{ .type = @intFromEnum(Ref.comptime_int_type) },
5717 .expr = .{ .int = .{ .value = 1 } },
5718 };
5719 },
5720 .negative_one => {
5721 return DocData.WalkResult{
5722 .typeRef = .{ .type = @intFromEnum(Ref.comptime_int_type) },
5723 .expr = .{ .int = .{ .value = 1, .negated = true } },
5724 };
5725 },
5726 .zero_usize => {
5727 return DocData.WalkResult{
5728 .typeRef = .{ .type = @intFromEnum(Ref.usize_type) },
5729 .expr = .{ .int = .{ .value = 0 } },
5730 };
5731 },
5732 .one_usize => {
5733 return DocData.WalkResult{
5734 .typeRef = .{ .type = @intFromEnum(Ref.usize_type) },
5735 .expr = .{ .int = .{ .value = 1 } },
5736 };
5737 },
5738 .zero_u8 => {
5739 return DocData.WalkResult{
5740 .typeRef = .{ .type = @intFromEnum(Ref.u8_type) },
5741 .expr = .{ .int = .{ .value = 0 } },
5742 };
5743 },
5744 .one_u8 => {
5745 return DocData.WalkResult{
5746 .typeRef = .{ .type = @intFromEnum(Ref.u8_type) },
5747 .expr = .{ .int = .{ .value = 1 } },
5748 };
5749 },
5750 .four_u8 => {
5751 return DocData.WalkResult{
5752 .typeRef = .{ .type = @intFromEnum(Ref.u8_type) },
5753 .expr = .{ .int = .{ .value = 4 } },
5754 };
5755 },
5756
5757 .void_value => {
5758 return DocData.WalkResult{
5759 .typeRef = .{ .type = @intFromEnum(Ref.void_type) },
5760 .expr = .{ .void = .{} },
5761 };
5762 },
5763 .unreachable_value => {
5764 return DocData.WalkResult{
5765 .typeRef = .{ .type = @intFromEnum(Ref.noreturn_type) },
5766 .expr = .{ .@"unreachable" = .{} },
5767 };
5768 },
5769 .null_value => {
5770 return DocData.WalkResult{ .expr = .null };
5771 },
5772 .bool_true => {
5773 return DocData.WalkResult{
5774 .typeRef = .{ .type = @intFromEnum(Ref.bool_type) },
5775 .expr = .{ .bool = true },
5776 };
5777 },
5778 .bool_false => {
5779 return DocData.WalkResult{
5780 .typeRef = .{ .type = @intFromEnum(Ref.bool_type) },
5781 .expr = .{ .bool = false },
5782 };
5783 },
5784 .empty_struct => {
5785 return DocData.WalkResult{ .expr = .{ .@"struct" = &.{} } };
5786 },
5787 .calling_convention_type => {
5788 return DocData.WalkResult{
5789 .typeRef = .{ .type = @intFromEnum(Ref.type_type) },
5790 .expr = .{ .type = @intFromEnum(Ref.calling_convention_type) },
5791 };
5792 },
5793 .calling_convention_c => {
5794 return DocData.WalkResult{
5795 .typeRef = .{ .type = @intFromEnum(Ref.calling_convention_type) },
5796 .expr = .{ .enumLiteral = "C" },
5797 };
5798 },
5799 .calling_convention_inline => {
5800 return DocData.WalkResult{
5801 .typeRef = .{ .type = @intFromEnum(Ref.calling_convention_type) },
5802 .expr = .{ .enumLiteral = "Inline" },
5803 };
5804 },
5805 // .generic_poison => {
5806 // return DocData.WalkResult{ .int = .{
5807 // .type = @intFromEnum(Ref.comptime_int_type),
5808 // .value = 1,
5809 // } };
5810 // },
5811 }
5812 }
5813}
5814
5815fn printWithContext(
5816 file: *File,
5817 inst: Zir.Inst.Index,
5818 comptime fmt: []const u8,
5819 args: anytype,
5820) void {
5821 return printWithOptionalContext(file, inst.toOptional(), fmt, args);
5822}
5823
5824fn printWithOptionalContext(file: *File, inst: Zir.Inst.OptionalIndex, comptime fmt: []const u8, args: anytype) void {
5825 log.debug("Context [{s}] % {} \n " ++ fmt, .{ file.sub_file_path, inst } ++ args);
5826}
5827
5828fn panicWithContext(
5829 file: *File,
5830 inst: Zir.Inst.Index,
5831 comptime fmt: []const u8,
5832 args: anytype,
5833) noreturn {
5834 printWithOptionalContext(file, inst.toOptional(), fmt, args);
5835 unreachable;
5836}
5837
5838fn panicWithOptionalContext(
5839 file: *File,
5840 inst: Zir.Inst.OptionalIndex,
5841 comptime fmt: []const u8,
5842 args: anytype,
5843) noreturn {
5844 printWithOptionalContext(file, inst, fmt, args);
5845 unreachable;
5846}
5847
5848fn cteTodo(self: *Autodoc, msg: []const u8) error{OutOfMemory}!DocData.WalkResult {
5849 const cte_slot_index = self.comptime_exprs.items.len;
5850 try self.comptime_exprs.append(self.arena, .{
5851 .code = msg,
5852 });
5853 return DocData.WalkResult{ .expr = .{ .comptimeExpr = cte_slot_index } };
5854}
5855
5856fn writeFileTableToJson(
5857 map: std.AutoArrayHashMapUnmanaged(*File, usize),
5858 mods: std.AutoArrayHashMapUnmanaged(*Module, DocData.DocModule),
5859 jsw: anytype,
5860) !void {
5861 try jsw.beginArray();
5862 var it = map.iterator();
5863 while (it.next()) |entry| {
5864 try jsw.beginArray();
5865 try jsw.write(entry.key_ptr.*.sub_file_path);
5866 try jsw.write(mods.getIndex(entry.key_ptr.*.mod) orelse 0);
5867 try jsw.endArray();
5868 }
5869 try jsw.endArray();
5870}
5871
5872/// Writes the data like so:
5873/// ```
5874/// {
5875/// "<section name>": [{name: "<guide name>", text: "<guide contents>"},],
5876/// }
5877/// ```
5878fn writeGuidesToJson(sections: std.ArrayListUnmanaged(Section), jsw: anytype) !void {
5879 try jsw.beginArray();
5880
5881 for (sections.items) |s| {
5882 // section name
5883 try jsw.beginObject();
5884 try jsw.objectField("name");
5885 try jsw.write(s.name);
5886 try jsw.objectField("guides");
5887
5888 // section value
5889 try jsw.beginArray();
5890 for (s.guides.items) |g| {
5891 try jsw.beginObject();
5892 try jsw.objectField("name");
5893 try jsw.write(g.name);
5894 try jsw.objectField("body");
5895 try jsw.write(g.body);
5896 try jsw.endObject();
5897 }
5898 try jsw.endArray();
5899 try jsw.endObject();
5900 }
5901
5902 try jsw.endArray();
5903}
5904
5905fn writeModuleTableToJson(
5906 map: std.AutoHashMapUnmanaged(*Module, DocData.DocModule.TableEntry),
5907 jsw: anytype,
5908) !void {
5909 try jsw.beginObject();
5910 var it = map.valueIterator();
5911 while (it.next()) |entry| {
5912 try jsw.objectField(entry.name);
5913 try jsw.write(entry.value);
5914 }
5915 try jsw.endObject();
5916}
5917
5918fn srcLocInfo(
5919 self: Autodoc,
5920 file: *File,
5921 src_node: i32,
5922 parent_src: SrcLocInfo,
5923) !SrcLocInfo {
5924 const sn = @as(u32, @intCast(@as(i32, @intCast(parent_src.src_node)) + src_node));
5925 const tree = try file.getTree(self.zcu.gpa);
5926 const node_idx = @as(Ast.Node.Index, @bitCast(sn));
5927 const tokens = tree.nodes.items(.main_token);
5928
5929 const tok_idx = tokens[node_idx];
5930 const start = tree.tokens.items(.start)[tok_idx];
5931 const loc = tree.tokenLocation(parent_src.bytes, tok_idx);
5932 return SrcLocInfo{
5933 .line = parent_src.line + loc.line,
5934 .bytes = start,
5935 .src_node = sn,
5936 };
5937}
5938
5939fn declIsVar(
5940 self: Autodoc,
5941 file: *File,
5942 src_node: i32,
5943 parent_src: SrcLocInfo,
5944) !bool {
5945 const sn = @as(u32, @intCast(@as(i32, @intCast(parent_src.src_node)) + src_node));
5946 const tree = try file.getTree(self.zcu.gpa);
5947 const node_idx = @as(Ast.Node.Index, @bitCast(sn));
5948 const tokens = tree.nodes.items(.main_token);
5949 const tags = tree.tokens.items(.tag);
5950
5951 const tok_idx = tokens[node_idx];
5952
5953 // tags[tok_idx] is the token called 'mut token' in AstGen
5954 return (tags[tok_idx] == .keyword_var);
5955}
5956
5957fn getBlockSource(
5958 self: Autodoc,
5959 file: *File,
5960 parent_src: SrcLocInfo,
5961 block_src_node: i32,
5962) AutodocErrors![]const u8 {
5963 const tree = try file.getTree(self.zcu.gpa);
5964 const block_src = try self.srcLocInfo(file, block_src_node, parent_src);
5965 return tree.getNodeSource(block_src.src_node);
5966}
5967
5968fn getTLDocComment(self: *Autodoc, file: *File) ![]const u8 {
5969 const source = (try file.getSource(self.zcu.gpa)).bytes;
5970 var tokenizer = Tokenizer.init(source);
5971 var tok = tokenizer.next();
5972 var comment = std.ArrayList(u8).init(self.arena);
5973 while (tok.tag == .container_doc_comment) : (tok = tokenizer.next()) {
5974 try comment.appendSlice(source[tok.loc.start + "//!".len .. tok.loc.end + 1]);
5975 }
5976
5977 return comment.items;
5978}
5979
5980/// Returns the doc comment cleared of autodoc directives.
5981fn findGuidePaths(self: *Autodoc, file: *File, str: []const u8) ![]const u8 {
5982 const guide_prefix = "zig-autodoc-guide:";
5983 const section_prefix = "zig-autodoc-section:";
5984
5985 try self.guide_sections.append(self.arena, .{}); // add a default section
5986 var current_section = &self.guide_sections.items[self.guide_sections.items.len - 1];
5987
5988 var clean_docs: std.ArrayListUnmanaged(u8) = .{};
5989 errdefer clean_docs.deinit(self.arena);
5990
5991 // TODO: this algo is kinda inefficient
5992
5993 var it = std.mem.splitScalar(u8, str, '\n');
5994 while (it.next()) |line| {
5995 const trimmed_line = std.mem.trim(u8, line, " ");
5996 if (std.mem.startsWith(u8, trimmed_line, guide_prefix)) {
5997 const path = trimmed_line[guide_prefix.len..];
5998 const trimmed_path = std.mem.trim(u8, path, " ");
5999 try self.addGuide(file, trimmed_path, current_section);
6000 } else if (std.mem.startsWith(u8, trimmed_line, section_prefix)) {
6001 const section_name = trimmed_line[section_prefix.len..];
6002 const trimmed_section_name = std.mem.trim(u8, section_name, " ");
6003 try self.guide_sections.append(self.arena, .{
6004 .name = trimmed_section_name,
6005 });
6006 current_section = &self.guide_sections.items[self.guide_sections.items.len - 1];
6007 } else {
6008 try clean_docs.appendSlice(self.arena, line);
6009 try clean_docs.append(self.arena, '\n');
6010 }
6011 }
6012
6013 return clean_docs.toOwnedSlice(self.arena);
6014}
6015
6016fn addGuide(self: *Autodoc, file: *File, guide_path: []const u8, section: *Section) !void {
6017 if (guide_path.len == 0) return error.MissingAutodocGuideName;
6018
6019 const resolved_path = try std.fs.path.resolve(self.arena, &[_][]const u8{
6020 file.sub_file_path, "..", guide_path,
6021 });
6022
6023 var guide_file = try file.mod.root.openFile(resolved_path, .{});
6024 defer guide_file.close();
6025
6026 const guide = guide_file.reader().readAllAlloc(self.arena, 1 * 1024 * 1024) catch |err| switch (err) {
6027 error.StreamTooLong => @panic("stream too long"),
6028 else => |e| return e,
6029 };
6030
6031 try section.guides.append(self.arena, .{
6032 .name = resolved_path,
6033 .body = guide,
6034 });
6035}
src/Compilation.zig-25
......@@ -36,7 +36,6 @@ const Cache = std.Build.Cache;
3636const c_codegen = @import("codegen/c.zig");
3737const libtsan = @import("libtsan.zig");
3838const Zir = std.zig.Zir;
39const Autodoc = @import("Autodoc.zig");
4039const resinator = @import("resinator.zig");
4140const Builtin = @import("Builtin.zig");
4241const LlvmObject = @import("codegen/llvm.zig").Object;
......@@ -2347,10 +2346,6 @@ fn flush(comp: *Compilation, arena: Allocator, prog_node: *std.Progress.Node) !v
23472346 try emitLlvmObject(comp, arena, default_emit, null, llvm_object, prog_node);
23482347 }
23492348 }
2350
2351 if (comp.totalErrorCount() == 0) {
2352 try maybeGenerateAutodocs(comp, prog_node);
2353 }
23542349}
23552350
23562351/// This function is called by the frontend before flush(). It communicates that
......@@ -2401,26 +2396,6 @@ fn renameTmpIntoCache(
24012396 }
24022397}
24032398
2404fn maybeGenerateAutodocs(comp: *Compilation, prog_node: *std.Progress.Node) !void {
2405 const mod = comp.module orelse return;
2406 // TODO: do this in a separate job during performAllTheWork(). The
2407 // file copies at the end of generate() can also be extracted to
2408 // separate jobs
2409 if (!build_options.only_c and !build_options.only_core_functionality) {
2410 if (comp.docs_emit) |emit| {
2411 var dir = try emit.directory.handle.makeOpenPath(emit.sub_path, .{});
2412 defer dir.close();
2413
2414 var sub_prog_node = prog_node.start("Generating documentation", 0);
2415 sub_prog_node.activate();
2416 sub_prog_node.context.refresh();
2417 defer sub_prog_node.end();
2418
2419 try Autodoc.generate(mod, dir);
2420 }
2421 }
2422}
2423
24242399/// Communicate the output binary location to parent Compilations.
24252400fn wholeCacheModeSetBinFilePath(
24262401 comp: *Compilation,
src/autodoc/render_source.zig deleted-435
......@@ -1,435 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const io = std.io;
4const fs = std.fs;
5const process = std.process;
6const ChildProcess = std.ChildProcess;
7const Progress = std.Progress;
8const print = std.debug.print;
9const mem = std.mem;
10const testing = std.testing;
11const Allocator = std.mem.Allocator;
12const Module = @import("../Module.zig");
13
14pub fn genHtml(
15 allocator: Allocator,
16 src: *Module.File,
17 out: anytype,
18) !void {
19 try out.writeAll(
20 \\<!doctype html>
21 \\<html lang="en">
22 \\<head>
23 \\ <meta charset="utf-8">
24 \\ <meta name="viewport" content="width=device-width, initial-scale=1.0">
25 );
26 try out.print(" <title>{s} - source view</title>\n", .{src.sub_file_path});
27 try out.writeAll(
28 \\ <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg==">
29 \\ <link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNTMgMTQwIj48ZyBmaWxsPSIjRjdBNDFEIj48Zz48cG9seWdvbiBwb2ludHM9IjQ2LDIyIDI4LDQ0IDE5LDMwIi8+PHBvbHlnb24gcG9pbnRzPSI0NiwyMiAzMywzMyAyOCw0NCAyMiw0NCAyMiw5NSAzMSw5NSAyMCwxMDAgMTIsMTE3IDAsMTE3IDAsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMzEsOTUgMTIsMTE3IDQsMTA2Ii8+PC9nPjxnPjxwb2x5Z29uIHBvaW50cz0iNTYsMjIgNjIsMzYgMzcsNDQiLz48cG9seWdvbiBwb2ludHM9IjU2LDIyIDExMSwyMiAxMTEsNDQgMzcsNDQgNTYsMzIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTE2LDk1IDk3LDExNyA5MCwxMDQiLz48cG9seWdvbiBwb2ludHM9IjExNiw5NSAxMDAsMTA0IDk3LDExNyA0MiwxMTcgNDIsOTUiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTUwLDAgNTIsMTE3IDMsMTQwIDEwMSwyMiIvPjwvZz48Zz48cG9seWdvbiBwb2ludHM9IjE0MSwyMiAxNDAsNDAgMTIyLDQ1Ii8+PHBvbHlnb24gcG9pbnRzPSIxNTMsMjIgMTUzLDExNyAxMDYsMTE3IDEyMCwxMDUgMTI1LDk1IDEzMSw5NSAxMzEsNDUgMTIyLDQ1IDEzMiwzNiAxNDEsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTI1LDk1IDEzMCwxMTAgMTA2LDExNyIvPjwvZz48L2c+PC9zdmc+">
30 \\ <style>
31 \\ body{
32 \\ font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif;
33 \\ margin: 0;
34 \\ line-height: 1.5;
35 \\ }
36 \\
37 \\ pre > code {
38 \\ display: block;
39 \\ overflow: auto;
40 \\ line-height: normal;
41 \\ margin: 0em;
42 \\ }
43 \\ .tok-kw {
44 \\ color: #333;
45 \\ font-weight: bold;
46 \\ }
47 \\ .tok-str {
48 \\ color: #d14;
49 \\ }
50 \\ .tok-builtin {
51 \\ color: #005C7A;
52 \\ }
53 \\ .tok-comment {
54 \\ color: #545454;
55 \\ font-style: italic;
56 \\ }
57 \\ .tok-fn {
58 \\ color: #900;
59 \\ font-weight: bold;
60 \\ }
61 \\ .tok-null {
62 \\ color: #005C5C;
63 \\ }
64 \\ .tok-number {
65 \\ color: #005C5C;
66 \\ }
67 \\ .tok-type {
68 \\ color: #458;
69 \\ font-weight: bold;
70 \\ }
71 \\ pre {
72 \\ counter-reset: line;
73 \\ }
74 \\ pre .line:before {
75 \\ counter-increment: line;
76 \\ content: counter(line);
77 \\ display: inline-block;
78 \\ padding-right: 1em;
79 \\ width: 2em;
80 \\ text-align: right;
81 \\ color: #999;
82 \\ }
83 \\
84 \\ .line {
85 \\ width: 100%;
86 \\ display: inline-block;
87 \\ }
88 \\ .line:target {
89 \\ border-top: 1px solid #ccc;
90 \\ border-bottom: 1px solid #ccc;
91 \\ background: #fafafa;
92 \\ }
93 \\
94 \\ @media (prefers-color-scheme: dark) {
95 \\ body{
96 \\ background:#222;
97 \\ color: #ccc;
98 \\ }
99 \\ pre > code {
100 \\ color: #ccc;
101 \\ background: #222;
102 \\ border: unset;
103 \\ }
104 \\ .line:target {
105 \\ border-top: 1px solid #444;
106 \\ border-bottom: 1px solid #444;
107 \\ background: #333;
108 \\ }
109 \\ .tok-kw {
110 \\ color: #eee;
111 \\ }
112 \\ .tok-str {
113 \\ color: #2e5;
114 \\ }
115 \\ .tok-builtin {
116 \\ color: #ff894c;
117 \\ }
118 \\ .tok-comment {
119 \\ color: #aa7;
120 \\ }
121 \\ .tok-fn {
122 \\ color: #B1A0F8;
123 \\ }
124 \\ .tok-null {
125 \\ color: #ff8080;
126 \\ }
127 \\ .tok-number {
128 \\ color: #ff8080;
129 \\ }
130 \\ .tok-type {
131 \\ color: #68f;
132 \\ }
133 \\ }
134 \\ </style>
135 \\</head>
136 \\<body>
137 \\
138 );
139
140 const source = try src.getSource(allocator);
141 try tokenizeAndPrintRaw(out, source.bytes);
142 try out.writeAll(
143 \\</body>
144 \\</html>
145 );
146}
147
148const start_line = "<span class=\"line\" id=\"L{d}\">";
149const end_line = "</span>\n";
150
151var line_counter: usize = 1;
152
153pub fn tokenizeAndPrintRaw(
154 out: anytype,
155 src: [:0]const u8,
156) !void {
157 line_counter = 1;
158
159 try out.print("<pre><code>" ++ start_line, .{line_counter});
160 var tokenizer = std.zig.Tokenizer.init(src);
161 var index: usize = 0;
162 var next_tok_is_fn = false;
163 while (true) {
164 const prev_tok_was_fn = next_tok_is_fn;
165 next_tok_is_fn = false;
166
167 const token = tokenizer.next();
168 if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| {
169 // render one comment
170 const comment_start = index + comment_start_off;
171 const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n");
172 const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start;
173
174 try writeEscapedLines(out, src[index..comment_start]);
175 try out.writeAll("<span class=\"tok-comment\">");
176 try writeEscaped(out, src[comment_start..comment_end]);
177 try out.writeAll("</span>\n");
178 index = comment_end;
179 tokenizer.index = index;
180 continue;
181 }
182
183 try writeEscapedLines(out, src[index..token.loc.start]);
184 switch (token.tag) {
185 .eof => break,
186
187 .keyword_addrspace,
188 .keyword_align,
189 .keyword_and,
190 .keyword_asm,
191 .keyword_async,
192 .keyword_await,
193 .keyword_break,
194 .keyword_catch,
195 .keyword_comptime,
196 .keyword_const,
197 .keyword_continue,
198 .keyword_defer,
199 .keyword_else,
200 .keyword_enum,
201 .keyword_errdefer,
202 .keyword_error,
203 .keyword_export,
204 .keyword_extern,
205 .keyword_for,
206 .keyword_if,
207 .keyword_inline,
208 .keyword_noalias,
209 .keyword_noinline,
210 .keyword_nosuspend,
211 .keyword_opaque,
212 .keyword_or,
213 .keyword_orelse,
214 .keyword_packed,
215 .keyword_anyframe,
216 .keyword_pub,
217 .keyword_resume,
218 .keyword_return,
219 .keyword_linksection,
220 .keyword_callconv,
221 .keyword_struct,
222 .keyword_suspend,
223 .keyword_switch,
224 .keyword_test,
225 .keyword_threadlocal,
226 .keyword_try,
227 .keyword_union,
228 .keyword_unreachable,
229 .keyword_usingnamespace,
230 .keyword_var,
231 .keyword_volatile,
232 .keyword_allowzero,
233 .keyword_while,
234 .keyword_anytype,
235 => {
236 try out.writeAll("<span class=\"tok-kw\">");
237 try writeEscaped(out, src[token.loc.start..token.loc.end]);
238 try out.writeAll("</span>");
239 },
240
241 .keyword_fn => {
242 try out.writeAll("<span class=\"tok-kw\">");
243 try writeEscaped(out, src[token.loc.start..token.loc.end]);
244 try out.writeAll("</span>");
245 next_tok_is_fn = true;
246 },
247
248 .string_literal,
249 .char_literal,
250 => {
251 try out.writeAll("<span class=\"tok-str\">");
252 try writeEscaped(out, src[token.loc.start..token.loc.end]);
253 try out.writeAll("</span>");
254 },
255
256 .multiline_string_literal_line => {
257 if (src[token.loc.end - 1] == '\n') {
258 try out.writeAll("<span class=\"tok-str\">");
259 try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]);
260 line_counter += 1;
261 try out.print("</span>" ++ end_line ++ "\n" ++ start_line, .{line_counter});
262 } else {
263 try out.writeAll("<span class=\"tok-str\">");
264 try writeEscaped(out, src[token.loc.start..token.loc.end]);
265 try out.writeAll("</span>");
266 }
267 },
268
269 .builtin => {
270 try out.writeAll("<span class=\"tok-builtin\">");
271 try writeEscaped(out, src[token.loc.start..token.loc.end]);
272 try out.writeAll("</span>");
273 },
274
275 .doc_comment,
276 .container_doc_comment,
277 => {
278 try out.writeAll("<span class=\"tok-comment\">");
279 try writeEscaped(out, src[token.loc.start..token.loc.end]);
280 try out.writeAll("</span>");
281 },
282
283 .identifier => {
284 const tok_bytes = src[token.loc.start..token.loc.end];
285 if (mem.eql(u8, tok_bytes, "undefined") or
286 mem.eql(u8, tok_bytes, "null") or
287 mem.eql(u8, tok_bytes, "true") or
288 mem.eql(u8, tok_bytes, "false"))
289 {
290 try out.writeAll("<span class=\"tok-null\">");
291 try writeEscaped(out, tok_bytes);
292 try out.writeAll("</span>");
293 } else if (prev_tok_was_fn) {
294 try out.writeAll("<span class=\"tok-fn\">");
295 try writeEscaped(out, tok_bytes);
296 try out.writeAll("</span>");
297 } else {
298 const is_int = blk: {
299 if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u')
300 break :blk false;
301 var i = token.loc.start + 1;
302 if (i == token.loc.end)
303 break :blk false;
304 while (i != token.loc.end) : (i += 1) {
305 if (src[i] < '0' or src[i] > '9')
306 break :blk false;
307 }
308 break :blk true;
309 };
310 if (is_int or isType(tok_bytes)) {
311 try out.writeAll("<span class=\"tok-type\">");
312 try writeEscaped(out, tok_bytes);
313 try out.writeAll("</span>");
314 } else {
315 try writeEscaped(out, tok_bytes);
316 }
317 }
318 },
319
320 .number_literal => {
321 try out.writeAll("<span class=\"tok-number\">");
322 try writeEscaped(out, src[token.loc.start..token.loc.end]);
323 try out.writeAll("</span>");
324 },
325
326 .bang,
327 .pipe,
328 .pipe_pipe,
329 .pipe_equal,
330 .equal,
331 .equal_equal,
332 .equal_angle_bracket_right,
333 .bang_equal,
334 .l_paren,
335 .r_paren,
336 .semicolon,
337 .percent,
338 .percent_equal,
339 .l_brace,
340 .r_brace,
341 .l_bracket,
342 .r_bracket,
343 .period,
344 .period_asterisk,
345 .ellipsis2,
346 .ellipsis3,
347 .caret,
348 .caret_equal,
349 .plus,
350 .plus_plus,
351 .plus_equal,
352 .plus_percent,
353 .plus_percent_equal,
354 .plus_pipe,
355 .plus_pipe_equal,
356 .minus,
357 .minus_equal,
358 .minus_percent,
359 .minus_percent_equal,
360 .minus_pipe,
361 .minus_pipe_equal,
362 .asterisk,
363 .asterisk_equal,
364 .asterisk_asterisk,
365 .asterisk_percent,
366 .asterisk_percent_equal,
367 .asterisk_pipe,
368 .asterisk_pipe_equal,
369 .arrow,
370 .colon,
371 .slash,
372 .slash_equal,
373 .comma,
374 .ampersand,
375 .ampersand_equal,
376 .question_mark,
377 .angle_bracket_left,
378 .angle_bracket_left_equal,
379 .angle_bracket_angle_bracket_left,
380 .angle_bracket_angle_bracket_left_equal,
381 .angle_bracket_angle_bracket_left_pipe,
382 .angle_bracket_angle_bracket_left_pipe_equal,
383 .angle_bracket_right,
384 .angle_bracket_right_equal,
385 .angle_bracket_angle_bracket_right,
386 .angle_bracket_angle_bracket_right_equal,
387 .tilde,
388 => try writeEscaped(out, src[token.loc.start..token.loc.end]),
389
390 .invalid, .invalid_periodasterisks => return error.ParseError,
391 }
392 index = token.loc.end;
393 }
394 try out.writeAll(end_line ++ "</code></pre>");
395}
396
397fn writeEscapedLines(out: anytype, text: []const u8) !void {
398 for (text) |char| {
399 if (char == '\n') {
400 try out.writeAll(end_line);
401 line_counter += 1;
402 try out.print(start_line, .{line_counter});
403 } else {
404 try writeEscaped(out, &[_]u8{char});
405 }
406 }
407}
408
409fn writeEscaped(out: anytype, input: []const u8) !void {
410 for (input) |c| {
411 try switch (c) {
412 '&' => out.writeAll("&amp;"),
413 '<' => out.writeAll("&lt;"),
414 '>' => out.writeAll("&gt;"),
415 '"' => out.writeAll("&quot;"),
416 else => out.writeByte(c),
417 };
418 }
419}
420
421const builtin_types = [_][]const u8{
422 "f16", "f32", "f64", "f80", "f128",
423 "c_longdouble", "c_short", "c_ushort", "c_int", "c_uint",
424 "c_long", "c_ulong", "c_longlong", "c_ulonglong", "c_char",
425 "anyopaque", "void", "bool", "isize", "usize",
426 "noreturn", "type", "anyerror", "comptime_int", "comptime_float",
427};
428
429fn isType(name: []const u8) bool {
430 for (builtin_types) |t| {
431 if (mem.eql(u8, t, name))
432 return true;
433 }
434 return false;
435}