master
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.StringArrayHashMapUnmanaged(File) = .empty;
14pub var decls: std.ArrayList(Decl) = .empty;
15pub var modules: std.StringArrayHashMapUnmanaged(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.AutoArrayHashMapUnmanaged(Ast.TokenIndex, Ast.Node.Index) = .empty,
46 /// Maps field access identifiers to the containing field access node.
47 token_parents: std.AutoArrayHashMapUnmanaged(Ast.TokenIndex, Ast.Node.Index) = .empty,
48 /// Maps declarations to their global index.
49 node_decls: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Decl.Index) = .empty,
50 /// Maps function declarations to doctests.
51 doctests: std.AutoArrayHashMapUnmanaged(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.AutoArrayHashMapUnmanaged(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 = @enumFromInt(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()[@intFromEnum(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()[@intFromEnum(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(@enumFromInt(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 = @enumFromInt(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, .zig);
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, "", .zig);
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.StringArrayHashMapUnmanaged(Ast.Node.Index) = .empty,
469 doctests: std.StringArrayHashMapUnmanaged(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 .array_mult,
713 .error_union,
714 .merge_error_sets,
715 .bool_and,
716 .bool_or,
717 .@"catch",
718 .@"orelse",
719 .array_type,
720 .array_access,
721 .switch_range,
722 => {
723 const lhs, const rhs = ast.nodeData(node).node_and_node;
724 try expr(w, scope, parent_decl, lhs);
725 try expr(w, scope, parent_decl, rhs);
726 },
727
728 .assign_destructure => {
729 const full = ast.assignDestructure(node);
730 for (full.ast.variables) |variable_node| try expr(w, scope, parent_decl, variable_node);
731 _ = try expr(w, scope, parent_decl, full.ast.value_expr);
732 },
733
734 .bool_not,
735 .bit_not,
736 .negation,
737 .negation_wrap,
738 .deref,
739 .address_of,
740 .optional_type,
741 .@"comptime",
742 .@"nosuspend",
743 .@"suspend",
744 .@"resume",
745 .@"try",
746 => try expr(w, scope, parent_decl, ast.nodeData(node).node),
747 .unwrap_optional,
748 .grouped_expression,
749 => try expr(w, scope, parent_decl, ast.nodeData(node).node_and_token[0]),
750 .@"return" => try maybe_expr(w, scope, parent_decl, ast.nodeData(node).opt_node),
751
752 .anyframe_type => try expr(w, scope, parent_decl, ast.nodeData(node).token_and_node[1]),
753 .@"break" => try maybe_expr(w, scope, parent_decl, ast.nodeData(node).opt_token_and_opt_node[1]),
754
755 .identifier => {
756 const ident_token = ast.nodeMainToken(node);
757 const ident_name = ast.tokenSlice(ident_token);
758 if (scope.lookup(ast, ident_name)) |var_node| {
759 try w.file.get().ident_decls.put(gpa, ident_token, var_node);
760 }
761 },
762 .field_access => {
763 const object_node, const field_ident = ast.nodeData(node).node_and_token;
764 try w.file.get().token_parents.put(gpa, field_ident, node);
765 // This will populate the left-most field object if it is an
766 // identifier, allowing rendering code to piece together the link.
767 try expr(w, scope, parent_decl, object_node);
768 },
769
770 .string_literal,
771 .multiline_string_literal,
772 .number_literal,
773 .unreachable_literal,
774 .enum_literal,
775 .error_value,
776 .anyframe_literal,
777 .@"continue",
778 .char_literal,
779 .error_set_decl,
780 => {},
781
782 .asm_simple,
783 .@"asm",
784 => {
785 const full = ast.fullAsm(node).?;
786 for (full.ast.items) |n| {
787 // There is a missing call here to expr() for .asm_input and
788 // .asm_output nodes.
789 _ = n;
790 }
791 try expr(w, scope, parent_decl, full.ast.template);
792 },
793
794 .asm_legacy => {},
795
796 .builtin_call_two,
797 .builtin_call_two_comma,
798 .builtin_call,
799 .builtin_call_comma,
800 => {
801 var buf: [2]Ast.Node.Index = undefined;
802 const params = ast.builtinCallParams(&buf, node).?;
803 return builtin_call(w, scope, parent_decl, node, params);
804 },
805
806 .call_one,
807 .call_one_comma,
808 .call,
809 .call_comma,
810 => {
811 var buf: [1]Ast.Node.Index = undefined;
812 const full = ast.fullCall(&buf, node).?;
813 try expr(w, scope, parent_decl, full.ast.fn_expr);
814 for (full.ast.params) |param| {
815 try expr(w, scope, parent_decl, param);
816 }
817 },
818
819 .if_simple,
820 .@"if",
821 => {
822 const full = ast.fullIf(node).?;
823 try expr(w, scope, parent_decl, full.ast.cond_expr);
824 try expr(w, scope, parent_decl, full.ast.then_expr);
825 try maybe_expr(w, scope, parent_decl, full.ast.else_expr);
826 },
827
828 .while_simple,
829 .while_cont,
830 .@"while",
831 => {
832 try while_expr(w, scope, parent_decl, ast.fullWhile(node).?);
833 },
834
835 .for_simple, .@"for" => {
836 const full = ast.fullFor(node).?;
837 for (full.ast.inputs) |input| {
838 if (ast.nodeTag(input) == .for_range) {
839 const start, const end = ast.nodeData(input).node_and_opt_node;
840 try expr(w, scope, parent_decl, start);
841 try maybe_expr(w, scope, parent_decl, end);
842 } else {
843 try expr(w, scope, parent_decl, input);
844 }
845 }
846 try expr(w, scope, parent_decl, full.ast.then_expr);
847 try maybe_expr(w, scope, parent_decl, full.ast.else_expr);
848 },
849
850 .slice => return slice(w, scope, parent_decl, ast.slice(node)),
851 .slice_open => return slice(w, scope, parent_decl, ast.sliceOpen(node)),
852 .slice_sentinel => return slice(w, scope, parent_decl, ast.sliceSentinel(node)),
853
854 .block_two,
855 .block_two_semicolon,
856 .block,
857 .block_semicolon,
858 => {
859 var buf: [2]Ast.Node.Index = undefined;
860 const statements = ast.blockStatements(&buf, node).?;
861 return block(w, scope, parent_decl, statements);
862 },
863
864 .ptr_type_aligned,
865 .ptr_type_sentinel,
866 .ptr_type,
867 .ptr_type_bit_range,
868 => {
869 const full = ast.fullPtrType(node).?;
870 try maybe_expr(w, scope, parent_decl, full.ast.align_node);
871 try maybe_expr(w, scope, parent_decl, full.ast.addrspace_node);
872 try maybe_expr(w, scope, parent_decl, full.ast.sentinel);
873 try maybe_expr(w, scope, parent_decl, full.ast.bit_range_start);
874 try maybe_expr(w, scope, parent_decl, full.ast.bit_range_end);
875 try expr(w, scope, parent_decl, full.ast.child_type);
876 },
877
878 .container_decl,
879 .container_decl_trailing,
880 .container_decl_arg,
881 .container_decl_arg_trailing,
882 .container_decl_two,
883 .container_decl_two_trailing,
884 .tagged_union,
885 .tagged_union_trailing,
886 .tagged_union_enum_tag,
887 .tagged_union_enum_tag_trailing,
888 .tagged_union_two,
889 .tagged_union_two_trailing,
890 => {
891 var buf: [2]Ast.Node.Index = undefined;
892 return struct_decl(w, scope, parent_decl, node, ast.fullContainerDecl(&buf, node).?);
893 },
894
895 .array_type_sentinel => {
896 const len_expr, const extra_index = ast.nodeData(node).node_and_extra;
897 const extra = ast.extraData(extra_index, Ast.Node.ArrayTypeSentinel);
898 try expr(w, scope, parent_decl, len_expr);
899 try expr(w, scope, parent_decl, extra.elem_type);
900 try expr(w, scope, parent_decl, extra.sentinel);
901 },
902 .@"switch", .switch_comma => {
903 const full = ast.fullSwitch(node).?;
904 try expr(w, scope, parent_decl, full.ast.condition);
905 for (full.ast.cases) |case_node| {
906 const case = ast.fullSwitchCase(case_node).?;
907 for (case.ast.values) |value_node| {
908 try expr(w, scope, parent_decl, value_node);
909 }
910 try expr(w, scope, parent_decl, case.ast.target_expr);
911 }
912 },
913
914 .array_init_one,
915 .array_init_one_comma,
916 .array_init_dot_two,
917 .array_init_dot_two_comma,
918 .array_init_dot,
919 .array_init_dot_comma,
920 .array_init,
921 .array_init_comma,
922 => {
923 var buf: [2]Ast.Node.Index = undefined;
924 const full = ast.fullArrayInit(&buf, node).?;
925 try maybe_expr(w, scope, parent_decl, full.ast.type_expr);
926 for (full.ast.elements) |elem| {
927 try expr(w, scope, parent_decl, elem);
928 }
929 },
930
931 .struct_init_one,
932 .struct_init_one_comma,
933 .struct_init_dot_two,
934 .struct_init_dot_two_comma,
935 .struct_init_dot,
936 .struct_init_dot_comma,
937 .struct_init,
938 .struct_init_comma,
939 => {
940 var buf: [2]Ast.Node.Index = undefined;
941 const full = ast.fullStructInit(&buf, node).?;
942 try maybe_expr(w, scope, parent_decl, full.ast.type_expr);
943 for (full.ast.fields) |field| {
944 try expr(w, scope, parent_decl, field);
945 }
946 },
947
948 .fn_proto_simple,
949 .fn_proto_multi,
950 .fn_proto_one,
951 .fn_proto,
952 => {
953 var buf: [1]Ast.Node.Index = undefined;
954 return fn_decl(w, scope, parent_decl, .none, ast.fullFnProto(&buf, node).?);
955 },
956 }
957}
958
959fn slice(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.Slice) Oom!void {
960 try expr(w, scope, parent_decl, full.ast.sliced);
961 try expr(w, scope, parent_decl, full.ast.start);
962 try maybe_expr(w, scope, parent_decl, full.ast.end);
963 try maybe_expr(w, scope, parent_decl, full.ast.sentinel);
964}
965
966fn builtin_call(
967 w: *Walk,
968 scope: *Scope,
969 parent_decl: Decl.Index,
970 node: Ast.Node.Index,
971 params: []const Ast.Node.Index,
972) Oom!void {
973 const ast = w.file.get_ast();
974 const builtin_token = ast.nodeMainToken(node);
975 const builtin_name = ast.tokenSlice(builtin_token);
976 if (std.mem.eql(u8, builtin_name, "@This")) {
977 try w.file.get().node_decls.put(gpa, node, scope.getNamespaceDecl());
978 }
979
980 for (params) |param| {
981 try expr(w, scope, parent_decl, param);
982 }
983}
984
985fn block(
986 w: *Walk,
987 parent_scope: *Scope,
988 parent_decl: Decl.Index,
989 statements: []const Ast.Node.Index,
990) Oom!void {
991 const ast = w.file.get_ast();
992
993 var scope = parent_scope;
994
995 for (statements) |node| {
996 switch (ast.nodeTag(node)) {
997 .global_var_decl,
998 .local_var_decl,
999 .simple_var_decl,
1000 .aligned_var_decl,
1001 => {
1002 const full = ast.fullVarDecl(node).?;
1003 try global_var_decl(w, scope, parent_decl, full);
1004 const local = try gpa.create(Scope.Local);
1005 local.* = .{
1006 .parent = scope,
1007 .var_node = node,
1008 };
1009 try w.file.get().scopes.putNoClobber(gpa, node, &local.base);
1010 scope = &local.base;
1011 },
1012
1013 .assign_destructure => {
1014 log.debug("walk assign_destructure not implemented yet", .{});
1015 },
1016
1017 .grouped_expression => try expr(w, scope, parent_decl, ast.nodeData(node).node_and_token[0]),
1018
1019 .@"defer" => try expr(w, scope, parent_decl, ast.nodeData(node).node),
1020 .@"errdefer" => try expr(w, scope, parent_decl, ast.nodeData(node).opt_token_and_node[1]),
1021
1022 else => try expr(w, scope, parent_decl, node),
1023 }
1024 }
1025}
1026
1027fn while_expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.While) Oom!void {
1028 try expr(w, scope, parent_decl, full.ast.cond_expr);
1029 try maybe_expr(w, scope, parent_decl, full.ast.cont_expr);
1030 try expr(w, scope, parent_decl, full.ast.then_expr);
1031 try maybe_expr(w, scope, parent_decl, full.ast.else_expr);
1032}
1033
1034fn scanDecls(w: *Walk, namespace: *Scope.Namespace, members: []const Ast.Node.Index) Oom!void {
1035 const ast = w.file.get_ast();
1036
1037 for (members) |member_node| {
1038 const name_token = switch (ast.nodeTag(member_node)) {
1039 .global_var_decl,
1040 .local_var_decl,
1041 .simple_var_decl,
1042 .aligned_var_decl,
1043 => ast.nodeMainToken(member_node) + 1,
1044
1045 .fn_proto_simple,
1046 .fn_proto_multi,
1047 .fn_proto_one,
1048 .fn_proto,
1049 .fn_decl,
1050 => blk: {
1051 const ident = ast.nodeMainToken(member_node) + 1;
1052 if (ast.tokenTag(ident) != .identifier) continue;
1053 break :blk ident;
1054 },
1055
1056 .test_decl => {
1057 const opt_ident_token = ast.nodeData(member_node).opt_token_and_node[0];
1058 if (opt_ident_token.unwrap()) |ident_token| {
1059 const is_doctest = ast.tokenTag(ident_token) == .identifier;
1060 if (is_doctest) {
1061 const token_bytes = ast.tokenSlice(ident_token);
1062 try namespace.doctests.put(gpa, token_bytes, member_node);
1063 }
1064 }
1065 continue;
1066 },
1067
1068 else => continue,
1069 };
1070
1071 const token_bytes = ast.tokenSlice(name_token);
1072 try namespace.names.put(gpa, token_bytes, member_node);
1073 }
1074}
1075
1076pub fn isPrimitiveNonType(name: []const u8) bool {
1077 return std.mem.eql(u8, name, "undefined") or
1078 std.mem.eql(u8, name, "null") or
1079 std.mem.eql(u8, name, "true") or
1080 std.mem.eql(u8, name, "false");
1081}
1082
1083//test {
1084// const gpa = std.testing.allocator;
1085//
1086// var arena_instance = std.heap.ArenaAllocator.init(gpa);
1087// defer arena_instance.deinit();
1088// const arena = arena_instance.allocator();
1089//
1090// // example test command:
1091// // zig test --dep input.zig -Mroot=src/Walk.zig -Minput.zig=/home/andy/dev/zig/lib/std/fs/File/zig
1092// var ast = try Ast.parse(gpa, @embedFile("input.zig"), .zig);
1093// defer ast.deinit(gpa);
1094//
1095// var w: Walk = .{
1096// .arena = arena,
1097// .token_links = .{},
1098// .ast = &ast,
1099// };
1100//
1101// try w.root();
1102//}
1103
1104fn shrinkToFit(m: anytype) void {
1105 m.shrinkAndFree(gpa, m.entries.len);
1106}