1//! Find and annotate identifiers with links to their declarations.
2
3const Walk = @This();
4const std = @import("std");
5const Ast = std.zig.Ast;
6const assert = std.debug.assert;
7const log = std.log;
8const gpa = std.heap.wasm_allocator;
9const Oom = error{OutOfMemory};
10
11pub const Decl = @import("Decl.zig");
12
13pub var files: std.array_hash_map.String(File) = .empty;
14pub var decls: std.ArrayList(Decl) = .empty;
15pub var modules: std.array_hash_map.String(File.Index) = .empty;
16
17file: File.Index,
18
19/// keep in sync with "CAT_" constants in main.js
20pub const Category = union(enum(u8)) {
21 /// A struct type used only to group declarations.
22 namespace: Ast.Node.Index,
23 /// A container type (struct, union, enum, opaque).
24 container: Ast.Node.Index,
25 global_variable: Ast.Node.Index,
26 /// A function that has not been detected as returning a type.
27 function: Ast.Node.Index,
28 primitive: Ast.Node.Index,
29 error_set: Ast.Node.Index,
30 global_const: Ast.Node.Index,
31 alias: Decl.Index,
32 /// A primitive identifier that is also a type.
33 type,
34 /// Specifically it is the literal `type`.
35 type_type,
36 /// A function that returns a type.
37 type_function: Ast.Node.Index,
38
39 pub const Tag = @typeInfo(Category).@"union".tag_type.?;
40};
41
42pub const File = struct {
43 ast: Ast,
44 /// Maps identifiers to the declarations they point to.
45 ident_decls: std.array_hash_map.Auto(Ast.TokenIndex, Ast.Node.Index) = .empty,
46 /// Maps field access identifiers to the containing field access node.
47 token_parents: std.array_hash_map.Auto(Ast.TokenIndex, Ast.Node.Index) = .empty,
48 /// Maps declarations to their global index.
49 node_decls: std.array_hash_map.Auto(Ast.Node.Index, Decl.Index) = .empty,
50 /// Maps function declarations to doctests.
51 doctests: std.array_hash_map.Auto(Ast.Node.Index, Ast.Node.Index) = .empty,
52 /// root node => its namespace scope
53 /// struct/union/enum/opaque decl node => its namespace scope
54 /// local var decl node => its local variable scope
55 scopes: std.array_hash_map.Auto(Ast.Node.Index, *Scope) = .empty,
56
57 pub fn lookup_token(file: *File, token: Ast.TokenIndex) Decl.Index {
58 const decl_node = file.ident_decls.get(token) orelse return .none;
59 return file.node_decls.get(decl_node) orelse return .none;
60 }
61
62 pub const Index = enum(u32) {
63 _,
64
65 fn add_decl(i: Index, node: Ast.Node.Index, parent_decl: Decl.Index) Oom!Decl.Index {
66 try decls.append(gpa, .{
67 .ast_node = node,
68 .file = i,
69 .parent = parent_decl,
70 });
71 const decl_index: Decl.Index = @fromBackingInt(@intCast(decls.items.len - 1));
72 try i.get().node_decls.put(gpa, node, decl_index);
73 return decl_index;
74 }
75
76 pub fn get(i: File.Index) *File {
77 return &files.values()[@backingInt(i)];
78 }
79
80 pub fn get_ast(i: File.Index) *Ast {
81 return &i.get().ast;
82 }
83
84 pub fn path(i: File.Index) []const u8 {
85 return files.keys()[@backingInt(i)];
86 }
87
88 pub fn findRootDecl(file_index: File.Index) Decl.Index {
89 return file_index.get().node_decls.values()[0];
90 }
91
92 pub fn categorize_decl(file_index: File.Index, node: Ast.Node.Index) Category {
93 const ast = file_index.get_ast();
94 switch (ast.nodeTag(node)) {
95 .root => {
96 for (ast.rootDecls()) |member| {
97 switch (ast.nodeTag(member)) {
98 .container_field_init,
99 .container_field_align,
100 .container_field,
101 => return .{ .container = node },
102 else => {},
103 }
104 }
105 return .{ .namespace = node };
106 },
107
108 .global_var_decl,
109 .local_var_decl,
110 .simple_var_decl,
111 .aligned_var_decl,
112 => {
113 const var_decl = ast.fullVarDecl(node).?;
114 if (ast.tokenTag(var_decl.ast.mut_token) == .keyword_var)
115 return .{ .global_variable = node };
116 const init_node = var_decl.ast.init_node.unwrap() orelse
117 return .{ .global_const = node };
118
119 return categorize_expr(file_index, init_node);
120 },
121
122 .fn_proto,
123 .fn_proto_multi,
124 .fn_proto_one,
125 .fn_proto_simple,
126 .fn_decl,
127 => {
128 var buf: [1]Ast.Node.Index = undefined;
129 const full = ast.fullFnProto(&buf, node).?;
130 return categorize_func(file_index, node, full);
131 },
132
133 else => unreachable,
134 }
135 }
136
137 pub fn categorize_func(
138 file_index: File.Index,
139 node: Ast.Node.Index,
140 full: Ast.full.FnProto,
141 ) Category {
142 return switch (categorize_expr(file_index, full.ast.return_type.unwrap().?)) {
143 .namespace, .container, .error_set, .type_type => .{ .type_function = node },
144 else => .{ .function = node },
145 };
146 }
147
148 pub fn categorize_expr_deep(file_index: File.Index, node: Ast.Node.Index) Category {
149 return switch (categorize_expr(file_index, node)) {
150 .alias => |aliasee| aliasee.get().categorize(),
151 else => |result| result,
152 };
153 }
154
155 pub fn categorize_expr(file_index: File.Index, node: Ast.Node.Index) Category {
156 const file = file_index.get();
157 const ast = file_index.get_ast();
158 //log.debug("categorize_expr tag {s}", .{@tagName(ast.nodeTag(node))});
159 return switch (ast.nodeTag(node)) {
160 .container_decl,
161 .container_decl_trailing,
162 .container_decl_arg,
163 .container_decl_arg_trailing,
164 .container_decl_two,
165 .container_decl_two_trailing,
166 .tagged_union,
167 .tagged_union_trailing,
168 .tagged_union_enum_tag,
169 .tagged_union_enum_tag_trailing,
170 .tagged_union_two,
171 .tagged_union_two_trailing,
172 => {
173 var buf: [2]Ast.Node.Index = undefined;
174 const container_decl = ast.fullContainerDecl(&buf, node).?;
175 if (ast.tokenTag(container_decl.ast.main_token) != .keyword_struct) {
176 return .{ .container = node };
177 }
178 for (container_decl.ast.members) |member| {
179 switch (ast.nodeTag(member)) {
180 .container_field_init,
181 .container_field_align,
182 .container_field,
183 => return .{ .container = node },
184 else => {},
185 }
186 }
187 return .{ .namespace = node };
188 },
189
190 .error_set_decl,
191 .merge_error_sets,
192 => .{ .error_set = node },
193
194 .identifier => {
195 const name_token = ast.nodeMainToken(node);
196 const ident_name = ast.tokenSlice(name_token);
197 if (std.mem.eql(u8, ident_name, "type"))
198 return .type_type;
199
200 if (isPrimitiveNonType(ident_name))
201 return .{ .primitive = node };
202
203 if (std.zig.primitives.isPrimitive(ident_name))
204 return .type;
205
206 if (file.ident_decls.get(name_token)) |decl_node| {
207 const decl_index = file.node_decls.get(decl_node) orelse .none;
208 if (decl_index != .none) return .{ .alias = decl_index };
209 return categorize_decl(file_index, decl_node);
210 }
211
212 return .{ .global_const = node };
213 },
214
215 .field_access => {
216 const object_node, const field_ident = ast.nodeData(node).node_and_token;
217 const field_name = ast.tokenSlice(field_ident);
218
219 switch (categorize_expr(file_index, object_node)) {
220 .alias => |aliasee| if (aliasee.get().get_child(field_name)) |decl_index| {
221 return .{ .alias = decl_index };
222 },
223 else => {},
224 }
225
226 return .{ .global_const = node };
227 },
228
229 .builtin_call_two,
230 .builtin_call_two_comma,
231 .builtin_call,
232 .builtin_call_comma,
233 => {
234 var buf: [2]Ast.Node.Index = undefined;
235 const params = ast.builtinCallParams(&buf, node).?;
236 return categorize_builtin_call(file_index, node, params);
237 },
238
239 .call_one,
240 .call_one_comma,
241 .call,
242 .call_comma,
243 => {
244 var buf: [1]Ast.Node.Index = undefined;
245 return categorize_call(file_index, node, ast.fullCall(&buf, node).?);
246 },
247
248 .if_simple,
249 .@"if",
250 => {
251 const if_full = ast.fullIf(node).?;
252 if (if_full.ast.else_expr.unwrap()) |else_expr| {
253 const then_cat = categorize_expr_deep(file_index, if_full.ast.then_expr);
254 const else_cat = categorize_expr_deep(file_index, else_expr);
255 if (then_cat == .type_type and else_cat == .type_type) {
256 return .type_type;
257 } else if (then_cat == .error_set and else_cat == .error_set) {
258 return .{ .error_set = node };
259 } else if (then_cat == .type or else_cat == .type or
260 then_cat == .namespace or else_cat == .namespace or
261 then_cat == .container or else_cat == .container or
262 then_cat == .error_set or else_cat == .error_set or
263 then_cat == .type_function or else_cat == .type_function)
264 {
265 return .type;
266 }
267 }
268 return .{ .global_const = node };
269 },
270
271 .@"switch", .switch_comma => return categorize_switch(file_index, node),
272
273 .optional_type,
274 .array_type,
275 .array_type_sentinel,
276 .ptr_type_aligned,
277 .ptr_type_sentinel,
278 .ptr_type,
279 .ptr_type_bit_range,
280 .anyframe_type,
281 => .type,
282
283 else => .{ .global_const = node },
284 };
285 }
286
287 fn categorize_call(
288 file_index: File.Index,
289 node: Ast.Node.Index,
290 call: Ast.full.Call,
291 ) Category {
292 return switch (categorize_expr(file_index, call.ast.fn_expr)) {
293 .type_function => .type,
294 .alias => |aliasee| categorize_decl_as_callee(aliasee, node),
295 else => .{ .global_const = node },
296 };
297 }
298
299 fn categorize_decl_as_callee(decl_index: Decl.Index, call_node: Ast.Node.Index) Category {
300 return switch (decl_index.get().categorize()) {
301 .type_function => .type,
302 .alias => |aliasee| categorize_decl_as_callee(aliasee, call_node),
303 else => .{ .global_const = call_node },
304 };
305 }
306
307 fn categorize_builtin_call(
308 file_index: File.Index,
309 node: Ast.Node.Index,
310 params: []const Ast.Node.Index,
311 ) Category {
312 const ast = file_index.get_ast();
313 const builtin_token = ast.nodeMainToken(node);
314 const builtin_name = ast.tokenSlice(builtin_token);
315 if (std.mem.eql(u8, builtin_name, "@import")) {
316 const str_lit_token = ast.nodeMainToken(params[0]);
317 const str_bytes = ast.tokenSlice(str_lit_token);
318 const file_path = std.zig.string_literal.parseAlloc(gpa, str_bytes) catch @panic("OOM");
319 defer gpa.free(file_path);
320 if (modules.get(file_path)) |imported_file_index| {
321 return .{ .alias = File.Index.findRootDecl(imported_file_index) };
322 }
323 const base_path = file_index.path();
324 const resolved_path = std.fs.path.resolvePosix(gpa, &.{
325 base_path, "..", file_path,
326 }) catch @panic("OOM");
327 defer gpa.free(resolved_path);
328 log.debug("from '{s}' @import '{s}' resolved='{s}'", .{
329 base_path, file_path, resolved_path,
330 });
331 if (files.getIndex(resolved_path)) |imported_file_index| {
332 return .{ .alias = File.Index.findRootDecl(@fromBackingInt(@intCast(imported_file_index))) };
333 } else {
334 log.warn("import target '{s}' did not resolve to any file", .{resolved_path});
335 }
336 } else if (std.mem.eql(u8, builtin_name, "@This")) {
337 if (file_index.get().node_decls.get(node)) |decl_index| {
338 return .{ .alias = decl_index };
339 } else {
340 log.warn("@This() is missing link to Decl.Index", .{});
341 }
342 }
343
344 return .{ .global_const = node };
345 }
346
347 fn categorize_switch(file_index: File.Index, node: Ast.Node.Index) Category {
348 const ast = file_index.get_ast();
349 const full = ast.fullSwitch(node).?;
350 var all_type_type = true;
351 var all_error_set = true;
352 var any_type = false;
353 if (full.ast.cases.len == 0) return .{ .global_const = node };
354 for (full.ast.cases) |case_node| {
355 const case = ast.fullSwitchCase(case_node).?;
356 switch (categorize_expr_deep(file_index, case.ast.target_expr)) {
357 .type_type => {
358 any_type = true;
359 all_error_set = false;
360 },
361 .error_set => {
362 any_type = true;
363 all_type_type = false;
364 },
365 .type, .namespace, .container, .type_function => {
366 any_type = true;
367 all_error_set = false;
368 all_type_type = false;
369 },
370 else => {
371 all_error_set = false;
372 all_type_type = false;
373 },
374 }
375 }
376 if (all_type_type) return .type_type;
377 if (all_error_set) return .{ .error_set = node };
378 if (any_type) return .type;
379 return .{ .global_const = node };
380 }
381 };
382};
383
384pub const ModuleIndex = enum(u32) {
385 _,
386};
387
388pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index {
389 const ast = try parse(file_name, bytes);
390 assert(ast.errors.len == 0);
391 const file_index: File.Index = @fromBackingInt(@intCast(files.entries.len));
392 try files.put(gpa, file_name, .{ .ast = ast });
393
394 var w: Walk = .{
395 .file = file_index,
396 };
397 const scope = try gpa.create(Scope);
398 scope.* = .{ .tag = .top };
399
400 const decl_index = try file_index.add_decl(.root, .none);
401 try struct_decl(&w, scope, decl_index, .root, ast.containerDeclRoot());
402
403 const file = file_index.get();
404 shrinkToFit(&file.ident_decls);
405 shrinkToFit(&file.token_parents);
406 shrinkToFit(&file.node_decls);
407 shrinkToFit(&file.doctests);
408 shrinkToFit(&file.scopes);
409
410 return file_index;
411}
412
413/// Parses a file and returns its `Ast`. If the file cannot be parsed, returns
414/// the `Ast` of an empty file, so that the rest of the Autodoc logic does not
415/// need to handle parse errors.
416fn parse(file_name: []const u8, source: []u8) Oom!Ast {
417 // Require every source file to end with a newline so that Zig's tokenizer
418 // can continue to require null termination and Autodoc implementation can
419 // avoid copying source bytes from the decompressed tar file buffer.
420 const adjusted_source: [:0]const u8 = s: {
421 if (source.len == 0)
422 break :s "";
423 if (source[source.len - 1] != '\n') {
424 log.err("{s}: expected newline at end of file", .{file_name});
425 break :s "";
426 }
427 source[source.len - 1] = 0;
428 break :s source[0 .. source.len - 1 :0];
429 };
430
431 var ast = try Ast.parse(gpa, adjusted_source, .{});
432 if (ast.errors.len > 0) {
433 defer ast.deinit(gpa);
434
435 const token_offsets = ast.tokens.items(.start);
436 var rendered_err: std.Io.Writer.Allocating = .init(gpa);
437 defer rendered_err.deinit();
438 for (ast.errors) |err| {
439 const err_offset = token_offsets[err.token] + ast.errorOffset(err);
440 const err_loc = std.zig.findLineColumn(ast.source, err_offset);
441 rendered_err.clearRetainingCapacity();
442 ast.renderError(err, &rendered_err.writer) catch |e| switch (e) {
443 error.WriteFailed => return error.OutOfMemory,
444 };
445 log.err("{s}:{d}:{d}: {s}", .{
446 file_name, err_loc.line + 1, err_loc.column + 1, rendered_err.written(),
447 });
448 }
449 return Ast.parse(gpa, "", .{});
450 }
451 return ast;
452}
453
454pub const Scope = struct {
455 tag: Tag,
456
457 const Tag = enum { top, local, namespace };
458
459 const Local = struct {
460 base: Scope = .{ .tag = .local },
461 parent: *Scope,
462 var_node: Ast.Node.Index,
463 };
464
465 const Namespace = struct {
466 base: Scope = .{ .tag = .namespace },
467 parent: *Scope,
468 names: std.array_hash_map.String(Ast.Node.Index) = .empty,
469 doctests: std.array_hash_map.String(Ast.Node.Index) = .empty,
470 decl_index: Decl.Index,
471 };
472
473 fn getNamespaceDecl(start_scope: *Scope) Decl.Index {
474 var it: *Scope = start_scope;
475 while (true) switch (it.tag) {
476 .top => unreachable,
477 .local => {
478 const local: *Local = @alignCast(@fieldParentPtr("base", it));
479 it = local.parent;
480 },
481 .namespace => {
482 const namespace: *Namespace = @alignCast(@fieldParentPtr("base", it));
483 return namespace.decl_index;
484 },
485 };
486 }
487
488 pub fn get_child(scope: *Scope, name: []const u8) ?Ast.Node.Index {
489 switch (scope.tag) {
490 .top, .local => return null,
491 .namespace => {
492 const namespace: *Namespace = @alignCast(@fieldParentPtr("base", scope));
493 return namespace.names.get(name);
494 },
495 }
496 }
497
498 pub fn lookup(start_scope: *Scope, ast: *const Ast, name: []const u8) ?Ast.Node.Index {
499 var it: *Scope = start_scope;
500 while (true) switch (it.tag) {
501 .top => break,
502 .local => {
503 const local: *Local = @alignCast(@fieldParentPtr("base", it));
504 const name_token = ast.nodeMainToken(local.var_node) + 1;
505 const ident_name = ast.tokenSlice(name_token);
506 if (std.mem.eql(u8, ident_name, name)) {
507 return local.var_node;
508 }
509 it = local.parent;
510 },
511 .namespace => {
512 const namespace: *Namespace = @alignCast(@fieldParentPtr("base", it));
513 if (namespace.names.get(name)) |node| {
514 return node;
515 }
516 it = namespace.parent;
517 },
518 };
519 return null;
520 }
521};
522
523fn struct_decl(
524 w: *Walk,
525 scope: *Scope,
526 parent_decl: Decl.Index,
527 node: Ast.Node.Index,
528 container_decl: Ast.full.ContainerDecl,
529) Oom!void {
530 const ast = w.file.get_ast();
531
532 const namespace = try gpa.create(Scope.Namespace);
533 namespace.* = .{
534 .parent = scope,
535 .decl_index = parent_decl,
536 };
537 try w.file.get().scopes.putNoClobber(gpa, node, &namespace.base);
538 try w.scanDecls(namespace, container_decl.ast.members);
539
540 for (container_decl.ast.members) |member| switch (ast.nodeTag(member)) {
541 .container_field_init,
542 .container_field_align,
543 .container_field,
544 => try w.container_field(&namespace.base, parent_decl, ast.fullContainerField(member).?),
545
546 .fn_proto,
547 .fn_proto_multi,
548 .fn_proto_one,
549 .fn_proto_simple,
550 .fn_decl,
551 => {
552 var buf: [1]Ast.Node.Index = undefined;
553 const full = ast.fullFnProto(&buf, member).?;
554 const fn_name_token = full.ast.fn_token + 1;
555 const fn_name = ast.tokenSlice(fn_name_token);
556 if (namespace.doctests.get(fn_name)) |doctest_node| {
557 try w.file.get().doctests.put(gpa, member, doctest_node);
558 }
559 const decl_index = try w.file.add_decl(member, parent_decl);
560 const body = if (ast.nodeTag(member) == .fn_decl) ast.nodeData(member).node_and_node[1].toOptional() else .none;
561 try w.fn_decl(&namespace.base, decl_index, body, full);
562 },
563
564 .global_var_decl,
565 .local_var_decl,
566 .simple_var_decl,
567 .aligned_var_decl,
568 => {
569 const decl_index = try w.file.add_decl(member, parent_decl);
570 try w.global_var_decl(&namespace.base, decl_index, ast.fullVarDecl(member).?);
571 },
572
573 .@"comptime",
574 => try w.expr(&namespace.base, parent_decl, ast.nodeData(member).node),
575
576 .test_decl => try w.expr(&namespace.base, parent_decl, ast.nodeData(member).opt_token_and_node[1]),
577
578 else => unreachable,
579 };
580}
581
582fn comptime_decl(
583 w: *Walk,
584 scope: *Scope,
585 parent_decl: Decl.Index,
586 full: Ast.full.VarDecl,
587) Oom!void {
588 try w.expr(scope, parent_decl, full.ast.type_node);
589 try w.maybe_expr(scope, parent_decl, full.ast.align_node);
590 try w.maybe_expr(scope, parent_decl, full.ast.addrspace_node);
591 try w.maybe_expr(scope, parent_decl, full.ast.section_node);
592 try w.expr(scope, parent_decl, full.ast.init_node);
593}
594
595fn global_var_decl(
596 w: *Walk,
597 scope: *Scope,
598 parent_decl: Decl.Index,
599 full: Ast.full.VarDecl,
600) Oom!void {
601 try w.maybe_expr(scope, parent_decl, full.ast.type_node);
602 try w.maybe_expr(scope, parent_decl, full.ast.align_node);
603 try w.maybe_expr(scope, parent_decl, full.ast.addrspace_node);
604 try w.maybe_expr(scope, parent_decl, full.ast.section_node);
605 try w.maybe_expr(scope, parent_decl, full.ast.init_node);
606}
607
608fn container_field(
609 w: *Walk,
610 scope: *Scope,
611 parent_decl: Decl.Index,
612 full: Ast.full.ContainerField,
613) Oom!void {
614 try w.maybe_expr(scope, parent_decl, full.ast.type_expr);
615 try w.maybe_expr(scope, parent_decl, full.ast.align_expr);
616 try w.maybe_expr(scope, parent_decl, full.ast.value_expr);
617}
618
619fn fn_decl(
620 w: *Walk,
621 scope: *Scope,
622 parent_decl: Decl.Index,
623 body: Ast.Node.OptionalIndex,
624 full: Ast.full.FnProto,
625) Oom!void {
626 for (full.ast.params) |param| {
627 try expr(w, scope, parent_decl, param);
628 }
629 try expr(w, scope, parent_decl, full.ast.return_type.unwrap().?);
630 try maybe_expr(w, scope, parent_decl, full.ast.align_expr);
631 try maybe_expr(w, scope, parent_decl, full.ast.addrspace_expr);
632 try maybe_expr(w, scope, parent_decl, full.ast.section_expr);
633 try maybe_expr(w, scope, parent_decl, full.ast.callconv_expr);
634 try maybe_expr(w, scope, parent_decl, body);
635}
636
637fn maybe_expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.OptionalIndex) Oom!void {
638 if (node.unwrap()) |n| return expr(w, scope, parent_decl, n);
639}
640
641fn expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.Index) Oom!void {
642 const ast = w.file.get_ast();
643 switch (ast.nodeTag(node)) {
644 .root => unreachable, // Top-level declaration.
645 .test_decl => unreachable, // Top-level declaration.
646 .container_field_init => unreachable, // Top-level declaration.
647 .container_field_align => unreachable, // Top-level declaration.
648 .container_field => unreachable, // Top-level declaration.
649 .fn_decl => unreachable, // Top-level declaration.
650
651 .global_var_decl => unreachable, // Handled in `block`.
652 .local_var_decl => unreachable, // Handled in `block`.
653 .simple_var_decl => unreachable, // Handled in `block`.
654 .aligned_var_decl => unreachable, // Handled in `block`.
655 .@"defer" => unreachable, // Handled in `block`.
656 .@"errdefer" => unreachable, // Handled in `block`.
657
658 .switch_case => unreachable, // Handled in `switchExpr`.
659 .switch_case_inline => unreachable, // Handled in `switchExpr`.
660 .switch_case_one => unreachable, // Handled in `switchExpr`.
661 .switch_case_inline_one => unreachable, // Handled in `switchExpr`.
662
663 .asm_output => unreachable, // Handled in `asmExpr`.
664 .asm_input => unreachable, // Handled in `asmExpr`.
665
666 .for_range => unreachable, // Handled in `forExpr`.
667
668 .assign,
669 .assign_shl,
670 .assign_shl_sat,
671 .assign_shr,
672 .assign_bit_and,
673 .assign_bit_or,
674 .assign_bit_xor,
675 .assign_div,
676 .assign_sub,
677 .assign_sub_wrap,
678 .assign_sub_sat,
679 .assign_mod,
680 .assign_add,
681 .assign_add_wrap,
682 .assign_add_sat,
683 .assign_mul,
684 .assign_mul_wrap,
685 .assign_mul_sat,
686 .shl,
687 .shr,
688 .add,
689 .add_wrap,
690 .add_sat,
691 .sub,
692 .sub_wrap,
693 .sub_sat,
694 .mul,
695 .mul_wrap,
696 .mul_sat,
697 .div,
698 .mod,
699 .shl_sat,
700
701 .bit_and,
702 .bit_or,
703 .bit_xor,
704 .bang_equal,
705 .equal_equal,
706 .greater_than,
707 .greater_or_equal,
708 .less_than,
709 .less_or_equal,
710 .array_cat,
711
712 .error_union,
713 .merge_error_sets,
714 .bool_and,
715 .bool_or,
716 .@"catch",
717 .@"orelse",
718 .array_type,
719 .array_access,
720 .switch_range,
721 => {
722 const lhs, const rhs = ast.nodeData(node).node_and_node;
723 try expr(w, scope, parent_decl, lhs);
724 try expr(w, scope, parent_decl, rhs);
725 },
726
727 .assign_destructure => {
728 const full = ast.assignDestructure(node);
729 for (full.ast.variables) |variable_node| try expr(w, scope, parent_decl, variable_node);
730 _ = try expr(w, scope, parent_decl, full.ast.value_expr);
731 },
732
733 .bool_not,
734 .bit_not,
735 .negation,
736 .negation_wrap,
737 .deref,
738 .address_of,
739 .optional_type,
740 .@"comptime",
741 .@"nosuspend",
742 .@"suspend",
743 .@"resume",
744 .@"try",
745 => try expr(w, scope, parent_decl, ast.nodeData(node).node),
746 .unwrap_optional,
747 .grouped_expression,
748 => try expr(w, scope, parent_decl, ast.nodeData(node).node_and_token[0]),
749 .@"return" => try maybe_expr(w, scope, parent_decl, ast.nodeData(node).opt_node),
750
751 .anyframe_type => try expr(w, scope, parent_decl, ast.nodeData(node).token_and_node[1]),
752 .@"break" => try maybe_expr(w, scope, parent_decl, ast.nodeData(node).opt_token_and_opt_node[1]),
753
754 .identifier => {
755 const ident_token = ast.nodeMainToken(node);
756 const ident_name = ast.tokenSlice(ident_token);
757 if (scope.lookup(ast, ident_name)) |var_node| {
758 try w.file.get().ident_decls.put(gpa, ident_token, var_node);
759 }
760 },
761 .field_access => {
762 const object_node, const field_ident = ast.nodeData(node).node_and_token;
763 try w.file.get().token_parents.put(gpa, field_ident, node);
764 // This will populate the left-most field object if it is an
765 // identifier, allowing rendering code to piece together the link.
766 try expr(w, scope, parent_decl, object_node);
767 },
768
769 .string_literal,
770 .multiline_string_literal,
771 .number_literal,
772 .unreachable_literal,
773 .enum_literal,
774 .error_value,
775 .anyframe_literal,
776 .@"continue",
777 .char_literal,
778 .error_set_decl,
779 => {},
780
781 .asm_simple,
782 .@"asm",
783 => {
784 const full = ast.fullAsm(node).?;
785 for (full.ast.items) |n| {
786 // There is a missing call here to expr() for .asm_input and
787 // .asm_output nodes.
788 _ = n;
789 }
790 try expr(w, scope, parent_decl, full.ast.template);
791 },
792
793 .builtin_call_two,
794 .builtin_call_two_comma,
795 .builtin_call,
796 .builtin_call_comma,
797 => {
798 var buf: [2]Ast.Node.Index = undefined;
799 const params = ast.builtinCallParams(&buf, node).?;
800 return builtin_call(w, scope, parent_decl, node, params);
801 },
802
803 .call_one,
804 .call_one_comma,
805 .call,
806 .call_comma,
807 => {
808 var buf: [1]Ast.Node.Index = undefined;
809 const full = ast.fullCall(&buf, node).?;
810 try expr(w, scope, parent_decl, full.ast.fn_expr);
811 for (full.ast.params) |param| {
812 try expr(w, scope, parent_decl, param);
813 }
814 },
815
816 .if_simple,
817 .@"if",
818 => {
819 const full = ast.fullIf(node).?;
820 try expr(w, scope, parent_decl, full.ast.cond_expr);
821 try expr(w, scope, parent_decl, full.ast.then_expr);
822 try maybe_expr(w, scope, parent_decl, full.ast.else_expr);
823 },
824
825 .while_simple,
826 .while_cont,
827 .@"while",
828 => {
829 try while_expr(w, scope, parent_decl, ast.fullWhile(node).?);
830 },
831
832 .for_simple, .@"for" => {
833 const full = ast.fullFor(node).?;
834 for (full.ast.inputs) |input| {
835 if (ast.nodeTag(input) == .for_range) {
836 const start, const end = ast.nodeData(input).node_and_opt_node;
837 try expr(w, scope, parent_decl, start);
838 try maybe_expr(w, scope, parent_decl, end);
839 } else {
840 try expr(w, scope, parent_decl, input);
841 }
842 }
843 try expr(w, scope, parent_decl, full.ast.then_expr);
844 try maybe_expr(w, scope, parent_decl, full.ast.else_expr);
845 },
846
847 .slice => return slice(w, scope, parent_decl, ast.slice(node)),
848 .slice_open => return slice(w, scope, parent_decl, ast.sliceOpen(node)),
849 .slice_sentinel => return slice(w, scope, parent_decl, ast.sliceSentinel(node)),
850
851 .block_two,
852 .block_two_semicolon,
853 .block,
854 .block_semicolon,
855 => {
856 var buf: [2]Ast.Node.Index = undefined;
857 const statements = ast.blockStatements(&buf, node).?;
858 return block(w, scope, parent_decl, statements);
859 },
860
861 .ptr_type_aligned,
862 .ptr_type_sentinel,
863 .ptr_type,
864 .ptr_type_bit_range,
865 => {
866 const full = ast.fullPtrType(node).?;
867 try maybe_expr(w, scope, parent_decl, full.ast.align_node);
868 try maybe_expr(w, scope, parent_decl, full.ast.addrspace_node);
869 try maybe_expr(w, scope, parent_decl, full.ast.sentinel);
870 try maybe_expr(w, scope, parent_decl, full.ast.bit_range_start);
871 try maybe_expr(w, scope, parent_decl, full.ast.bit_range_end);
872 try expr(w, scope, parent_decl, full.ast.child_type);
873 },
874
875 .container_decl,
876 .container_decl_trailing,
877 .container_decl_arg,
878 .container_decl_arg_trailing,
879 .container_decl_two,
880 .container_decl_two_trailing,
881 .tagged_union,
882 .tagged_union_trailing,
883 .tagged_union_enum_tag,
884 .tagged_union_enum_tag_trailing,
885 .tagged_union_two,
886 .tagged_union_two_trailing,
887 => {
888 var buf: [2]Ast.Node.Index = undefined;
889 return struct_decl(w, scope, parent_decl, node, ast.fullContainerDecl(&buf, node).?);
890 },
891
892 .array_type_sentinel => {
893 const len_expr, const extra_index = ast.nodeData(node).node_and_extra;
894 const extra = ast.extraData(extra_index, Ast.Node.ArrayTypeSentinel);
895 try expr(w, scope, parent_decl, len_expr);
896 try expr(w, scope, parent_decl, extra.elem_type);
897 try expr(w, scope, parent_decl, extra.sentinel);
898 },
899 .@"switch", .switch_comma => {
900 const full = ast.fullSwitch(node).?;
901 try expr(w, scope, parent_decl, full.ast.condition);
902 for (full.ast.cases) |case_node| {
903 const case = ast.fullSwitchCase(case_node).?;
904 for (case.ast.values) |value_node| {
905 try expr(w, scope, parent_decl, value_node);
906 }
907 try expr(w, scope, parent_decl, case.ast.target_expr);
908 }
909 },
910
911 .array_init_one,
912 .array_init_one_comma,
913 .array_init_dot_two,
914 .array_init_dot_two_comma,
915 .array_init_dot,
916 .array_init_dot_comma,
917 .array_init,
918 .array_init_comma,
919 => {
920 var buf: [2]Ast.Node.Index = undefined;
921 const full = ast.fullArrayInit(&buf, node).?;
922 try maybe_expr(w, scope, parent_decl, full.ast.type_expr);
923 for (full.ast.elements) |elem| {
924 try expr(w, scope, parent_decl, elem);
925 }
926 },
927
928 .struct_init_one,
929 .struct_init_one_comma,
930 .struct_init_dot_two,
931 .struct_init_dot_two_comma,
932 .struct_init_dot,
933 .struct_init_dot_comma,
934 .struct_init,
935 .struct_init_comma,
936 => {
937 var buf: [2]Ast.Node.Index = undefined;
938 const full = ast.fullStructInit(&buf, node).?;
939 try maybe_expr(w, scope, parent_decl, full.ast.type_expr);
940 for (full.ast.fields) |field| {
941 try expr(w, scope, parent_decl, field);
942 }
943 },
944
945 .fn_proto_simple,
946 .fn_proto_multi,
947 .fn_proto_one,
948 .fn_proto,
949 => {
950 var buf: [1]Ast.Node.Index = undefined;
951 return fn_decl(w, scope, parent_decl, .none, ast.fullFnProto(&buf, node).?);
952 },
953 }
954}
955
956fn slice(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.Slice) Oom!void {
957 try expr(w, scope, parent_decl, full.ast.sliced);
958 try expr(w, scope, parent_decl, full.ast.start);
959 try maybe_expr(w, scope, parent_decl, full.ast.end);
960 try maybe_expr(w, scope, parent_decl, full.ast.sentinel);
961}
962
963fn builtin_call(
964 w: *Walk,
965 scope: *Scope,
966 parent_decl: Decl.Index,
967 node: Ast.Node.Index,
968 params: []const Ast.Node.Index,
969) Oom!void {
970 const ast = w.file.get_ast();
971 const builtin_token = ast.nodeMainToken(node);
972 const builtin_name = ast.tokenSlice(builtin_token);
973 if (std.mem.eql(u8, builtin_name, "@This")) {
974 try w.file.get().node_decls.put(gpa, node, scope.getNamespaceDecl());
975 }
976
977 for (params) |param| {
978 try expr(w, scope, parent_decl, param);
979 }
980}
981
982fn block(
983 w: *Walk,
984 parent_scope: *Scope,
985 parent_decl: Decl.Index,
986 statements: []const Ast.Node.Index,
987) Oom!void {
988 const ast = w.file.get_ast();
989
990 var scope = parent_scope;
991
992 for (statements) |node| {
993 switch (ast.nodeTag(node)) {
994 .global_var_decl,
995 .local_var_decl,
996 .simple_var_decl,
997 .aligned_var_decl,
998 => {
999 const full = ast.fullVarDecl(node).?;
1000 try global_var_decl(w, scope, parent_decl, full);
1001 const local = try gpa.create(Scope.Local);
1002 local.* = .{
1003 .parent = scope,
1004 .var_node = node,
1005 };
1006 try w.file.get().scopes.putNoClobber(gpa, node, &local.base);
1007 scope = &local.base;
1008 },
1009
1010 .assign_destructure => {
1011 log.debug("walk assign_destructure not implemented yet", .{});
1012 },
1013
1014 .grouped_expression => try expr(w, scope, parent_decl, ast.nodeData(node).node_and_token[0]),
1015
1016 .@"defer", .@"errdefer" => try expr(w, scope, parent_decl, ast.nodeData(node).node),
1017
1018 else => try expr(w, scope, parent_decl, node),
1019 }
1020 }
1021}
1022
1023fn while_expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.While) Oom!void {
1024 try expr(w, scope, parent_decl, full.ast.cond_expr);
1025 try maybe_expr(w, scope, parent_decl, full.ast.cont_expr);
1026 try expr(w, scope, parent_decl, full.ast.then_expr);
1027 try maybe_expr(w, scope, parent_decl, full.ast.else_expr);
1028}
1029
1030fn scanDecls(w: *Walk, namespace: *Scope.Namespace, members: []const Ast.Node.Index) Oom!void {
1031 const ast = w.file.get_ast();
1032
1033 for (members) |member_node| {
1034 const name_token = switch (ast.nodeTag(member_node)) {
1035 .global_var_decl,
1036 .local_var_decl,
1037 .simple_var_decl,
1038 .aligned_var_decl,
1039 => ast.nodeMainToken(member_node) + 1,
1040
1041 .fn_proto_simple,
1042 .fn_proto_multi,
1043 .fn_proto_one,
1044 .fn_proto,
1045 .fn_decl,
1046 => blk: {
1047 const ident = ast.nodeMainToken(member_node) + 1;
1048 if (ast.tokenTag(ident) != .identifier) continue;
1049 break :blk ident;
1050 },
1051
1052 .test_decl => {
1053 const opt_ident_token = ast.nodeData(member_node).opt_token_and_node[0];
1054 if (opt_ident_token.unwrap()) |ident_token| {
1055 const is_doctest = ast.tokenTag(ident_token) == .identifier;
1056 if (is_doctest) {
1057 const token_bytes = ast.tokenSlice(ident_token);
1058 try namespace.doctests.put(gpa, token_bytes, member_node);
1059 }
1060 }
1061 continue;
1062 },
1063
1064 else => continue,
1065 };
1066
1067 const token_bytes = ast.tokenSlice(name_token);
1068 try namespace.names.put(gpa, token_bytes, member_node);
1069 }
1070}
1071
1072pub fn isPrimitiveNonType(name: []const u8) bool {
1073 return std.mem.eql(u8, name, "undefined") or
1074 std.mem.eql(u8, name, "null") or
1075 std.mem.eql(u8, name, "true") or
1076 std.mem.eql(u8, name, "false");
1077}
1078
1079//test {
1080// const gpa = std.testing.allocator;
1081//
1082// var arena_instance = std.heap.ArenaAllocator.init(gpa);
1083// defer arena_instance.deinit();
1084// const arena = arena_instance.allocator();
1085//
1086// // example test command:
1087// // zig test --dep input.zig -Mroot=src/Walk.zig -Minput.zig=/home/andy/dev/zig/lib/std/fs/File/zig
1088// var ast = try Ast.parse(gpa, @embedFile("input.zig"), .{});
1089// defer ast.deinit(gpa);
1090//
1091// var w: Walk = .{
1092// .arena = arena,
1093// .token_links = .{},
1094// .ast = &ast,
1095// };
1096//
1097// try w.root();
1098//}
1099
1100fn shrinkToFit(m: anytype) void {
1101 m.shrinkAndFree(gpa, m.entries.len);
1102}