zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

Walk.zig (40114B) - Raw


      1 //! Find and annotate identifiers with links to their declarations.
      2 
      3 const Walk = @This();
      4 const std = @import("std");
      5 const Ast = std.zig.Ast;
      6 const assert = std.debug.assert;
      7 const log = std.log;
      8 const gpa = std.heap.wasm_allocator;
      9 const Oom = error{OutOfMemory};
     10 
     11 pub const Decl = @import("Decl.zig");
     12 
     13 pub var files: std.StringArrayHashMapUnmanaged(File) = .empty;
     14 pub var decls: std.ArrayListUnmanaged(Decl) = .empty;
     15 pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .empty;
     16 
     17 file: File.Index,
     18 
     19 /// keep in sync with "CAT_" constants in main.js
     20 pub 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 
     42 pub 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 
    384 pub const ModuleIndex = enum(u32) {
    385     _,
    386 };
    387 
    388 pub 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.
    416 fn 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 
    454 pub 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 
    523 fn 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 
    582 fn 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 
    595 fn 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 
    608 fn 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 
    619 fn 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 
    637 fn 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 
    641 fn 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 
    959 fn 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 
    966 fn 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 
    985 fn 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 
   1027 fn 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 
   1034 fn 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 
   1076 pub 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 
   1104 fn shrinkToFit(m: anytype) void {
   1105     m.shrinkAndFree(gpa, m.entries.len);
   1106 }