zig

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

Walk.zig (32099B) - Raw


      1 const std = @import("std");
      2 const Ast = std.zig.Ast;
      3 const Walk = @This();
      4 const assert = std.debug.assert;
      5 const BuiltinFn = std.zig.BuiltinFn;
      6 
      7 ast: *const Ast,
      8 transformations: *std.array_list.Managed(Transformation),
      9 unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index),
     10 in_scope_names: std.StringArrayHashMapUnmanaged(u32),
     11 replace_names: std.StringArrayHashMapUnmanaged(u32),
     12 gpa: std.mem.Allocator,
     13 arena: std.mem.Allocator,
     14 
     15 pub const Transformation = union(enum) {
     16     /// Replace the fn decl AST Node with one whose body is only `@trap()` with
     17     /// discarded parameters.
     18     gut_function: Ast.Node.Index,
     19     /// Omit a global declaration.
     20     delete_node: Ast.Node.Index,
     21     /// Delete a local variable declaration and replace all of its references
     22     /// with `undefined`.
     23     delete_var_decl: struct {
     24         var_decl_node: Ast.Node.Index,
     25         /// Identifier nodes that reference the variable.
     26         references: std.ArrayListUnmanaged(Ast.Node.Index),
     27     },
     28     /// Replace an expression with `undefined`.
     29     replace_with_undef: Ast.Node.Index,
     30     /// Replace an expression with `true`.
     31     replace_with_true: Ast.Node.Index,
     32     /// Replace an expression with `false`.
     33     replace_with_false: Ast.Node.Index,
     34     /// Replace a node with another node.
     35     replace_node: struct {
     36         to_replace: Ast.Node.Index,
     37         replacement: Ast.Node.Index,
     38     },
     39     /// Replace an `@import` with the imported file contents wrapped in a struct.
     40     inline_imported_file: InlineImportedFile,
     41 
     42     pub const InlineImportedFile = struct {
     43         builtin_call_node: Ast.Node.Index,
     44         imported_string: []const u8,
     45         /// Identifier names that must be renamed in the inlined code or else
     46         /// will cause ambiguous reference errors.
     47         in_scope_names: std.StringArrayHashMapUnmanaged(void),
     48     };
     49 };
     50 
     51 pub const Error = error{OutOfMemory};
     52 
     53 /// The result will be priority shuffled.
     54 pub fn findTransformations(
     55     arena: std.mem.Allocator,
     56     ast: *const Ast,
     57     transformations: *std.array_list.Managed(Transformation),
     58 ) !void {
     59     transformations.clearRetainingCapacity();
     60 
     61     var walk: Walk = .{
     62         .ast = ast,
     63         .transformations = transformations,
     64         .gpa = transformations.allocator,
     65         .arena = arena,
     66         .unreferenced_globals = .{},
     67         .in_scope_names = .{},
     68         .replace_names = .{},
     69     };
     70     defer {
     71         walk.unreferenced_globals.deinit(walk.gpa);
     72         walk.in_scope_names.deinit(walk.gpa);
     73         walk.replace_names.deinit(walk.gpa);
     74     }
     75 
     76     try walkMembers(&walk, walk.ast.rootDecls());
     77 
     78     const unreferenced_globals = walk.unreferenced_globals.values();
     79     try transformations.ensureUnusedCapacity(unreferenced_globals.len);
     80     for (unreferenced_globals) |node| {
     81         transformations.appendAssumeCapacity(.{ .delete_node = node });
     82     }
     83 }
     84 
     85 fn walkMembers(w: *Walk, members: []const Ast.Node.Index) Error!void {
     86     // First we scan for globals so that we can delete them while walking.
     87     try scanDecls(w, members, .add);
     88 
     89     for (members) |member| {
     90         try walkMember(w, member);
     91     }
     92 
     93     try scanDecls(w, members, .remove);
     94 }
     95 
     96 const ScanDeclsAction = enum { add, remove };
     97 
     98 fn scanDecls(w: *Walk, members: []const Ast.Node.Index, action: ScanDeclsAction) Error!void {
     99     const ast = w.ast;
    100     const gpa = w.gpa;
    101 
    102     for (members) |member_node| {
    103         const name_token = switch (ast.nodeTag(member_node)) {
    104             .global_var_decl,
    105             .local_var_decl,
    106             .simple_var_decl,
    107             .aligned_var_decl,
    108             => ast.nodeMainToken(member_node) + 1,
    109 
    110             .fn_proto_simple,
    111             .fn_proto_multi,
    112             .fn_proto_one,
    113             .fn_proto,
    114             .fn_decl,
    115             => ast.nodeMainToken(member_node) + 1,
    116 
    117             else => continue,
    118         };
    119 
    120         assert(ast.tokenTag(name_token) == .identifier);
    121         const name_bytes = ast.tokenSlice(name_token);
    122 
    123         switch (action) {
    124             .add => {
    125                 try w.unreferenced_globals.put(gpa, name_bytes, member_node);
    126 
    127                 const gop = try w.in_scope_names.getOrPut(gpa, name_bytes);
    128                 if (!gop.found_existing) gop.value_ptr.* = 0;
    129                 gop.value_ptr.* += 1;
    130             },
    131             .remove => {
    132                 const entry = w.in_scope_names.getEntry(name_bytes).?;
    133                 if (entry.value_ptr.* <= 1) {
    134                     assert(w.in_scope_names.swapRemove(name_bytes));
    135                 } else {
    136                     entry.value_ptr.* -= 1;
    137                 }
    138             },
    139         }
    140     }
    141 }
    142 
    143 fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
    144     const ast = w.ast;
    145     switch (ast.nodeTag(decl)) {
    146         .fn_decl => {
    147             const fn_proto, const body_node = ast.nodeData(decl).node_and_node;
    148             try walkExpression(w, fn_proto);
    149             if (!isFnBodyGutted(ast, body_node)) {
    150                 w.replace_names.clearRetainingCapacity();
    151                 try w.transformations.append(.{ .gut_function = decl });
    152                 try walkExpression(w, body_node);
    153             }
    154         },
    155         .fn_proto_simple,
    156         .fn_proto_multi,
    157         .fn_proto_one,
    158         .fn_proto,
    159         => {
    160             try walkExpression(w, decl);
    161         },
    162 
    163         .global_var_decl,
    164         .local_var_decl,
    165         .simple_var_decl,
    166         .aligned_var_decl,
    167         => try walkGlobalVarDecl(w, decl, ast.fullVarDecl(decl).?),
    168 
    169         .test_decl => {
    170             try w.transformations.append(.{ .delete_node = decl });
    171             try walkExpression(w, ast.nodeData(decl).opt_token_and_node[1]);
    172         },
    173 
    174         .container_field_init,
    175         .container_field_align,
    176         .container_field,
    177         => {
    178             try w.transformations.append(.{ .delete_node = decl });
    179             try walkContainerField(w, ast.fullContainerField(decl).?);
    180         },
    181 
    182         .@"comptime" => {
    183             try w.transformations.append(.{ .delete_node = decl });
    184             try walkExpression(w, decl);
    185         },
    186 
    187         .root => unreachable,
    188         else => unreachable,
    189     }
    190 }
    191 
    192 fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
    193     const ast = w.ast;
    194     switch (ast.nodeTag(node)) {
    195         .identifier => {
    196             const name_ident = ast.nodeMainToken(node);
    197             assert(ast.tokenTag(name_ident) == .identifier);
    198             const name_bytes = ast.tokenSlice(name_ident);
    199             _ = w.unreferenced_globals.swapRemove(name_bytes);
    200             if (w.replace_names.get(name_bytes)) |index| {
    201                 try w.transformations.items[index].delete_var_decl.references.append(w.arena, node);
    202             }
    203         },
    204 
    205         .number_literal,
    206         .char_literal,
    207         .unreachable_literal,
    208         .anyframe_literal,
    209         .string_literal,
    210         => {},
    211 
    212         .multiline_string_literal => {},
    213 
    214         .error_value => {},
    215 
    216         .block_two,
    217         .block_two_semicolon,
    218         .block,
    219         .block_semicolon,
    220         => {
    221             var buf: [2]Ast.Node.Index = undefined;
    222             const statements = ast.blockStatements(&buf, node).?;
    223             return walkBlock(w, node, statements);
    224         },
    225 
    226         .@"errdefer" => {
    227             const expr = ast.nodeData(node).opt_token_and_node[1];
    228             return walkExpression(w, expr);
    229         },
    230 
    231         .@"defer",
    232         .@"comptime",
    233         .@"nosuspend",
    234         .@"suspend",
    235         => {
    236             return walkExpression(w, ast.nodeData(node).node);
    237         },
    238 
    239         .field_access => {
    240             try walkExpression(w, ast.nodeData(node).node_and_token[0]);
    241         },
    242 
    243         .for_range => {
    244             const start, const opt_end = ast.nodeData(node).node_and_opt_node;
    245             try walkExpression(w, start);
    246             if (opt_end.unwrap()) |end| {
    247                 return walkExpression(w, end);
    248             }
    249         },
    250 
    251         .add,
    252         .add_wrap,
    253         .add_sat,
    254         .array_cat,
    255         .array_mult,
    256         .assign,
    257         .assign_bit_and,
    258         .assign_bit_or,
    259         .assign_shl,
    260         .assign_shl_sat,
    261         .assign_shr,
    262         .assign_bit_xor,
    263         .assign_div,
    264         .assign_sub,
    265         .assign_sub_wrap,
    266         .assign_sub_sat,
    267         .assign_mod,
    268         .assign_add,
    269         .assign_add_wrap,
    270         .assign_add_sat,
    271         .assign_mul,
    272         .assign_mul_wrap,
    273         .assign_mul_sat,
    274         .bang_equal,
    275         .bit_and,
    276         .bit_or,
    277         .shl,
    278         .shl_sat,
    279         .shr,
    280         .bit_xor,
    281         .bool_and,
    282         .bool_or,
    283         .div,
    284         .equal_equal,
    285         .greater_or_equal,
    286         .greater_than,
    287         .less_or_equal,
    288         .less_than,
    289         .merge_error_sets,
    290         .mod,
    291         .mul,
    292         .mul_wrap,
    293         .mul_sat,
    294         .sub,
    295         .sub_wrap,
    296         .sub_sat,
    297         .@"catch",
    298         .error_union,
    299         .switch_range,
    300         .@"orelse",
    301         .array_access,
    302         => {
    303             const lhs, const rhs = ast.nodeData(node).node_and_node;
    304             try walkExpression(w, lhs);
    305             try walkExpression(w, rhs);
    306         },
    307 
    308         .assign_destructure => {
    309             const full = ast.assignDestructure(node);
    310             for (full.ast.variables) |variable_node| {
    311                 switch (ast.nodeTag(variable_node)) {
    312                     .global_var_decl,
    313                     .local_var_decl,
    314                     .simple_var_decl,
    315                     .aligned_var_decl,
    316                     => try walkLocalVarDecl(w, ast.fullVarDecl(variable_node).?),
    317 
    318                     else => try walkExpression(w, variable_node),
    319                 }
    320             }
    321             return walkExpression(w, full.ast.value_expr);
    322         },
    323 
    324         .bit_not,
    325         .bool_not,
    326         .negation,
    327         .negation_wrap,
    328         .optional_type,
    329         .address_of,
    330         .@"try",
    331         .@"resume",
    332         .deref,
    333         => {
    334             return walkExpression(w, ast.nodeData(node).node);
    335         },
    336 
    337         .array_type,
    338         .array_type_sentinel,
    339         => {},
    340 
    341         .ptr_type_aligned,
    342         .ptr_type_sentinel,
    343         .ptr_type,
    344         .ptr_type_bit_range,
    345         => {},
    346 
    347         .array_init_one,
    348         .array_init_one_comma,
    349         .array_init_dot_two,
    350         .array_init_dot_two_comma,
    351         .array_init_dot,
    352         .array_init_dot_comma,
    353         .array_init,
    354         .array_init_comma,
    355         => {
    356             var elements: [2]Ast.Node.Index = undefined;
    357             return walkArrayInit(w, ast.fullArrayInit(&elements, node).?);
    358         },
    359 
    360         .struct_init_one,
    361         .struct_init_one_comma,
    362         .struct_init_dot_two,
    363         .struct_init_dot_two_comma,
    364         .struct_init_dot,
    365         .struct_init_dot_comma,
    366         .struct_init,
    367         .struct_init_comma,
    368         => {
    369             var buf: [2]Ast.Node.Index = undefined;
    370             return walkStructInit(w, node, ast.fullStructInit(&buf, node).?);
    371         },
    372 
    373         .call_one,
    374         .call_one_comma,
    375         .call,
    376         .call_comma,
    377         => {
    378             var buf: [1]Ast.Node.Index = undefined;
    379             return walkCall(w, ast.fullCall(&buf, node).?);
    380         },
    381 
    382         .slice_open, .slice, .slice_sentinel => return walkSlice(w, node, ast.fullSlice(node).?),
    383 
    384         .unwrap_optional => {
    385             try walkExpression(w, ast.nodeData(node).node_and_token[0]);
    386         },
    387 
    388         .@"break" => {
    389             const label_token, const target = ast.nodeData(node).opt_token_and_opt_node;
    390             if (label_token == .none and target == .none) {
    391                 // no expressions
    392             } else if (label_token == .none and target != .none) {
    393                 try walkExpression(w, target.unwrap().?);
    394             } else if (label_token != .none and target == .none) {
    395                 try walkIdentifier(w, label_token.unwrap().?);
    396             } else if (label_token != .none and target != .none) {
    397                 try walkExpression(w, target.unwrap().?);
    398             }
    399         },
    400 
    401         .@"continue" => {
    402             const opt_label = ast.nodeData(node).opt_token_and_opt_node[0];
    403             if (opt_label.unwrap()) |label| {
    404                 return walkIdentifier(w, label);
    405             }
    406         },
    407 
    408         .@"return" => {
    409             if (ast.nodeData(node).opt_node.unwrap()) |lhs| {
    410                 try walkExpression(w, lhs);
    411             }
    412         },
    413 
    414         .grouped_expression => {
    415             try walkExpression(w, ast.nodeData(node).node_and_token[0]);
    416         },
    417 
    418         .container_decl,
    419         .container_decl_trailing,
    420         .container_decl_arg,
    421         .container_decl_arg_trailing,
    422         .container_decl_two,
    423         .container_decl_two_trailing,
    424         .tagged_union,
    425         .tagged_union_trailing,
    426         .tagged_union_enum_tag,
    427         .tagged_union_enum_tag_trailing,
    428         .tagged_union_two,
    429         .tagged_union_two_trailing,
    430         => {
    431             var buf: [2]Ast.Node.Index = undefined;
    432             return walkContainerDecl(w, node, ast.fullContainerDecl(&buf, node).?);
    433         },
    434 
    435         .error_set_decl => {
    436             const lbrace, const rbrace = ast.nodeData(node).token_and_token;
    437 
    438             var i = lbrace + 1;
    439             while (i < rbrace) : (i += 1) {
    440                 switch (ast.tokenTag(i)) {
    441                     .doc_comment => unreachable, // TODO
    442                     .identifier => try walkIdentifier(w, i),
    443                     .comma => {},
    444                     else => unreachable,
    445                 }
    446             }
    447         },
    448 
    449         .builtin_call_two,
    450         .builtin_call_two_comma,
    451         .builtin_call,
    452         .builtin_call_comma,
    453         => {
    454             var buf: [2]Ast.Node.Index = undefined;
    455             const params = ast.builtinCallParams(&buf, node).?;
    456             return walkBuiltinCall(w, node, params);
    457         },
    458 
    459         .fn_proto_simple,
    460         .fn_proto_multi,
    461         .fn_proto_one,
    462         .fn_proto,
    463         => {
    464             var buf: [1]Ast.Node.Index = undefined;
    465             return walkFnProto(w, ast.fullFnProto(&buf, node).?);
    466         },
    467 
    468         .anyframe_type => {
    469             _, const child_type = ast.nodeData(node).token_and_node;
    470             return walkExpression(w, child_type);
    471         },
    472 
    473         .@"switch",
    474         .switch_comma,
    475         => {
    476             const full = ast.fullSwitch(node).?;
    477             try walkExpression(w, full.ast.condition); // condition expression
    478             try walkExpressions(w, full.ast.cases);
    479         },
    480 
    481         .switch_case_one,
    482         .switch_case_inline_one,
    483         .switch_case,
    484         .switch_case_inline,
    485         => return walkSwitchCase(w, ast.fullSwitchCase(node).?),
    486 
    487         .while_simple,
    488         .while_cont,
    489         .@"while",
    490         => return walkWhile(w, node, ast.fullWhile(node).?),
    491 
    492         .for_simple,
    493         .@"for",
    494         => return walkFor(w, ast.fullFor(node).?),
    495 
    496         .if_simple,
    497         .@"if",
    498         => return walkIf(w, node, ast.fullIf(node).?),
    499 
    500         .asm_simple,
    501         .@"asm",
    502         => return walkAsm(w, ast.fullAsm(node).?),
    503 
    504         .asm_legacy => {
    505             return walkAsmLegacy(w, ast.legacyAsm(node).?);
    506         },
    507 
    508         .enum_literal => {
    509             return walkIdentifier(w, ast.nodeMainToken(node)); // name
    510         },
    511 
    512         .fn_decl => unreachable,
    513         .container_field => unreachable,
    514         .container_field_init => unreachable,
    515         .container_field_align => unreachable,
    516         .root => unreachable,
    517         .global_var_decl => unreachable,
    518         .local_var_decl => unreachable,
    519         .simple_var_decl => unreachable,
    520         .aligned_var_decl => unreachable,
    521         .test_decl => unreachable,
    522         .asm_output => unreachable,
    523         .asm_input => unreachable,
    524     }
    525 }
    526 
    527 fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.VarDecl) Error!void {
    528     _ = decl_node;
    529 
    530     if (var_decl.ast.type_node.unwrap()) |type_node| {
    531         try walkExpression(w, type_node);
    532     }
    533 
    534     if (var_decl.ast.align_node.unwrap()) |align_node| {
    535         try walkExpression(w, align_node);
    536     }
    537 
    538     if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
    539         try walkExpression(w, addrspace_node);
    540     }
    541 
    542     if (var_decl.ast.section_node.unwrap()) |section_node| {
    543         try walkExpression(w, section_node);
    544     }
    545 
    546     if (var_decl.ast.init_node.unwrap()) |init_node| {
    547         if (!isUndefinedIdent(w.ast, init_node)) {
    548             try w.transformations.append(.{ .replace_with_undef = init_node });
    549         }
    550         try walkExpression(w, init_node);
    551     }
    552 }
    553 
    554 fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
    555     try walkIdentifierNew(w, var_decl.ast.mut_token + 1); // name
    556 
    557     if (var_decl.ast.type_node.unwrap()) |type_node| {
    558         try walkExpression(w, type_node);
    559     }
    560 
    561     if (var_decl.ast.align_node.unwrap()) |align_node| {
    562         try walkExpression(w, align_node);
    563     }
    564 
    565     if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
    566         try walkExpression(w, addrspace_node);
    567     }
    568 
    569     if (var_decl.ast.section_node.unwrap()) |section_node| {
    570         try walkExpression(w, section_node);
    571     }
    572 
    573     if (var_decl.ast.init_node.unwrap()) |init_node| {
    574         if (!isUndefinedIdent(w.ast, init_node)) {
    575             try w.transformations.append(.{ .replace_with_undef = init_node });
    576         }
    577         try walkExpression(w, init_node);
    578     }
    579 }
    580 
    581 fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
    582     if (field.ast.type_expr.unwrap()) |type_expr| {
    583         try walkExpression(w, type_expr); // type
    584     }
    585     if (field.ast.align_expr.unwrap()) |align_expr| {
    586         try walkExpression(w, align_expr); // alignment
    587     }
    588     if (field.ast.value_expr.unwrap()) |value_expr| {
    589         try walkExpression(w, value_expr); // value
    590     }
    591 }
    592 
    593 fn walkBlock(
    594     w: *Walk,
    595     block_node: Ast.Node.Index,
    596     statements: []const Ast.Node.Index,
    597 ) Error!void {
    598     _ = block_node;
    599     const ast = w.ast;
    600 
    601     for (statements) |stmt| {
    602         switch (ast.nodeTag(stmt)) {
    603             .global_var_decl,
    604             .local_var_decl,
    605             .simple_var_decl,
    606             .aligned_var_decl,
    607             => {
    608                 const var_decl = ast.fullVarDecl(stmt).?;
    609                 if (var_decl.ast.init_node != .none and
    610                     isUndefinedIdent(w.ast, var_decl.ast.init_node.unwrap().?))
    611                 {
    612                     try w.transformations.append(.{ .delete_var_decl = .{
    613                         .var_decl_node = stmt,
    614                         .references = .{},
    615                     } });
    616                     const name_tok = var_decl.ast.mut_token + 1;
    617                     const name_bytes = ast.tokenSlice(name_tok);
    618                     try w.replace_names.put(w.gpa, name_bytes, @intCast(w.transformations.items.len - 1));
    619                 } else {
    620                     try walkLocalVarDecl(w, var_decl);
    621                 }
    622             },
    623 
    624             else => {
    625                 switch (categorizeStmt(ast, stmt)) {
    626                     // Don't try to remove `_ = foo;` discards; those are handled separately.
    627                     .discard_identifier => {},
    628                     // definitely try to remove `_ = undefined;` though.
    629                     .discard_undefined, .trap_call, .other => {
    630                         try w.transformations.append(.{ .delete_node = stmt });
    631                     },
    632                 }
    633                 try walkExpression(w, stmt);
    634             },
    635         }
    636     }
    637 }
    638 
    639 fn walkArrayType(w: *Walk, array_type: Ast.full.ArrayType) Error!void {
    640     try walkExpression(w, array_type.ast.elem_count);
    641     if (array_type.ast.sentinel.unwrap()) |sentinel| {
    642         try walkExpression(w, sentinel);
    643     }
    644     return walkExpression(w, array_type.ast.elem_type);
    645 }
    646 
    647 fn walkArrayInit(w: *Walk, array_init: Ast.full.ArrayInit) Error!void {
    648     if (array_init.ast.type_expr.unwrap()) |type_expr| {
    649         try walkExpression(w, type_expr); // T
    650     }
    651     for (array_init.ast.elements) |elem_init| {
    652         try walkExpression(w, elem_init);
    653     }
    654 }
    655 
    656 fn walkStructInit(
    657     w: *Walk,
    658     struct_node: Ast.Node.Index,
    659     struct_init: Ast.full.StructInit,
    660 ) Error!void {
    661     _ = struct_node;
    662     if (struct_init.ast.type_expr.unwrap()) |type_expr| {
    663         try walkExpression(w, type_expr); // T
    664     }
    665     for (struct_init.ast.fields) |field_init| {
    666         try walkExpression(w, field_init);
    667     }
    668 }
    669 
    670 fn walkCall(w: *Walk, call: Ast.full.Call) Error!void {
    671     try walkExpression(w, call.ast.fn_expr);
    672     try walkExpressions(w, call.ast.params);
    673 }
    674 
    675 fn walkSlice(
    676     w: *Walk,
    677     slice_node: Ast.Node.Index,
    678     slice: Ast.full.Slice,
    679 ) Error!void {
    680     _ = slice_node;
    681     try walkExpression(w, slice.ast.sliced);
    682     try walkExpression(w, slice.ast.start);
    683     if (slice.ast.end.unwrap()) |end| {
    684         try walkExpression(w, end);
    685     }
    686     if (slice.ast.sentinel.unwrap()) |sentinel| {
    687         try walkExpression(w, sentinel);
    688     }
    689 }
    690 
    691 fn walkIdentifier(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
    692     const ast = w.ast;
    693     assert(ast.tokenTag(name_ident) == .identifier);
    694     const name_bytes = ast.tokenSlice(name_ident);
    695     _ = w.unreferenced_globals.swapRemove(name_bytes);
    696 }
    697 
    698 fn walkIdentifierNew(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
    699     _ = w;
    700     _ = name_ident;
    701 }
    702 
    703 fn walkContainerDecl(
    704     w: *Walk,
    705     container_decl_node: Ast.Node.Index,
    706     container_decl: Ast.full.ContainerDecl,
    707 ) Error!void {
    708     _ = container_decl_node;
    709     if (container_decl.ast.arg.unwrap()) |arg| {
    710         try walkExpression(w, arg);
    711     }
    712     try walkMembers(w, container_decl.ast.members);
    713 }
    714 
    715 fn walkBuiltinCall(
    716     w: *Walk,
    717     call_node: Ast.Node.Index,
    718     params: []const Ast.Node.Index,
    719 ) Error!void {
    720     const ast = w.ast;
    721     const builtin_token = ast.nodeMainToken(call_node);
    722     const builtin_name = ast.tokenSlice(builtin_token);
    723     const info = BuiltinFn.list.get(builtin_name).?;
    724     switch (info.tag) {
    725         .import => {
    726             const operand_node = params[0];
    727             const str_lit_token = ast.nodeMainToken(operand_node);
    728             const token_bytes = ast.tokenSlice(str_lit_token);
    729             if (std.mem.endsWith(u8, token_bytes, ".zig\"")) {
    730                 const imported_string = std.zig.string_literal.parseAlloc(w.arena, token_bytes) catch
    731                     unreachable;
    732                 try w.transformations.append(.{ .inline_imported_file = .{
    733                     .builtin_call_node = call_node,
    734                     .imported_string = imported_string,
    735                     .in_scope_names = try std.StringArrayHashMapUnmanaged(void).init(
    736                         w.arena,
    737                         w.in_scope_names.keys(),
    738                         &.{},
    739                     ),
    740                 } });
    741             }
    742         },
    743         else => {},
    744     }
    745     for (params) |param_node| {
    746         try walkExpression(w, param_node);
    747     }
    748 }
    749 
    750 fn walkFnProto(w: *Walk, fn_proto: Ast.full.FnProto) Error!void {
    751     const ast = w.ast;
    752 
    753     {
    754         var it = fn_proto.iterate(ast);
    755         while (it.next()) |param| {
    756             if (param.type_expr) |type_expr| {
    757                 try walkExpression(w, type_expr);
    758             }
    759         }
    760     }
    761 
    762     if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
    763         try walkExpression(w, align_expr);
    764     }
    765 
    766     if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
    767         try walkExpression(w, addrspace_expr);
    768     }
    769 
    770     if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
    771         try walkExpression(w, section_expr);
    772     }
    773 
    774     if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
    775         try walkExpression(w, callconv_expr);
    776     }
    777 
    778     const return_type = fn_proto.ast.return_type.unwrap().?;
    779     try walkExpression(w, return_type);
    780 }
    781 
    782 fn walkExpressions(w: *Walk, expressions: []const Ast.Node.Index) Error!void {
    783     for (expressions) |expression| {
    784         try walkExpression(w, expression);
    785     }
    786 }
    787 
    788 fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {
    789     for (switch_case.ast.values) |value_expr| {
    790         try walkExpression(w, value_expr);
    791     }
    792     try walkExpression(w, switch_case.ast.target_expr);
    793 }
    794 
    795 fn walkWhile(w: *Walk, node_index: Ast.Node.Index, while_node: Ast.full.While) Error!void {
    796     // Perform these transformations in this priority order:
    797     // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
    798     // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
    799     // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
    800     // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
    801     if (!isTrueIdent(w.ast, while_node.ast.cond_expr) and
    802         (while_node.ast.else_expr == .none or isEmptyBlock(w.ast, while_node.ast.else_expr.unwrap().?)))
    803     {
    804         try w.transformations.ensureUnusedCapacity(1);
    805         w.transformations.appendAssumeCapacity(.{ .replace_with_true = while_node.ast.cond_expr });
    806     } else if (!isFalseIdent(w.ast, while_node.ast.cond_expr) and isEmptyBlock(w.ast, while_node.ast.then_expr)) {
    807         try w.transformations.ensureUnusedCapacity(1);
    808         w.transformations.appendAssumeCapacity(.{ .replace_with_false = while_node.ast.cond_expr });
    809     } else if (isTrueIdent(w.ast, while_node.ast.cond_expr)) {
    810         try w.transformations.ensureUnusedCapacity(1);
    811         w.transformations.appendAssumeCapacity(.{ .replace_node = .{
    812             .to_replace = node_index,
    813             .replacement = while_node.ast.then_expr,
    814         } });
    815     } else if (isFalseIdent(w.ast, while_node.ast.cond_expr)) {
    816         try w.transformations.ensureUnusedCapacity(1);
    817         w.transformations.appendAssumeCapacity(.{ .replace_node = .{
    818             .to_replace = node_index,
    819             .replacement = while_node.ast.else_expr.unwrap().?,
    820         } });
    821     }
    822 
    823     try walkExpression(w, while_node.ast.cond_expr); // condition
    824 
    825     if (while_node.ast.cont_expr.unwrap()) |cont_expr| {
    826         try walkExpression(w, cont_expr);
    827     }
    828 
    829     try walkExpression(w, while_node.ast.then_expr);
    830 
    831     if (while_node.ast.else_expr.unwrap()) |else_expr| {
    832         try walkExpression(w, else_expr);
    833     }
    834 }
    835 
    836 fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
    837     try walkExpressions(w, for_node.ast.inputs);
    838     try walkExpression(w, for_node.ast.then_expr);
    839     if (for_node.ast.else_expr.unwrap()) |else_expr| {
    840         try walkExpression(w, else_expr);
    841     }
    842 }
    843 
    844 fn walkIf(w: *Walk, node_index: Ast.Node.Index, if_node: Ast.full.If) Error!void {
    845     // Perform these transformations in this priority order:
    846     // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
    847     // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
    848     // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
    849     // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
    850     if (!isTrueIdent(w.ast, if_node.ast.cond_expr) and
    851         (if_node.ast.else_expr == .none or isEmptyBlock(w.ast, if_node.ast.else_expr.unwrap().?)))
    852     {
    853         try w.transformations.ensureUnusedCapacity(1);
    854         w.transformations.appendAssumeCapacity(.{ .replace_with_true = if_node.ast.cond_expr });
    855     } else if (!isFalseIdent(w.ast, if_node.ast.cond_expr) and isEmptyBlock(w.ast, if_node.ast.then_expr)) {
    856         try w.transformations.ensureUnusedCapacity(1);
    857         w.transformations.appendAssumeCapacity(.{ .replace_with_false = if_node.ast.cond_expr });
    858     } else if (isTrueIdent(w.ast, if_node.ast.cond_expr)) {
    859         try w.transformations.ensureUnusedCapacity(1);
    860         w.transformations.appendAssumeCapacity(.{ .replace_node = .{
    861             .to_replace = node_index,
    862             .replacement = if_node.ast.then_expr,
    863         } });
    864     } else if (isFalseIdent(w.ast, if_node.ast.cond_expr)) {
    865         try w.transformations.ensureUnusedCapacity(1);
    866         w.transformations.appendAssumeCapacity(.{ .replace_node = .{
    867             .to_replace = node_index,
    868             .replacement = if_node.ast.else_expr.unwrap().?,
    869         } });
    870     }
    871 
    872     try walkExpression(w, if_node.ast.cond_expr); // condition
    873     try walkExpression(w, if_node.ast.then_expr);
    874     if (if_node.ast.else_expr.unwrap()) |else_expr| {
    875         try walkExpression(w, else_expr);
    876     }
    877 }
    878 
    879 fn walkAsm(w: *Walk, asm_node: Ast.full.Asm) Error!void {
    880     try walkExpression(w, asm_node.ast.template);
    881     try walkExpressions(w, asm_node.ast.items);
    882 }
    883 
    884 fn walkAsmLegacy(w: *Walk, asm_node: Ast.full.AsmLegacy) Error!void {
    885     try walkExpression(w, asm_node.ast.template);
    886     try walkExpressions(w, asm_node.ast.items);
    887 }
    888 
    889 /// Check if it is already gutted (i.e. its body replaced with `@trap()`).
    890 fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool {
    891     // skip over discards
    892     var statements_buf: [2]Ast.Node.Index = undefined;
    893     const statements = switch (ast.nodeTag(body_node)) {
    894         .block_two,
    895         .block_two_semicolon,
    896         .block,
    897         .block_semicolon,
    898         => ast.blockStatements(&statements_buf, body_node).?,
    899 
    900         else => return false,
    901     };
    902     var i: usize = 0;
    903     while (i < statements.len) : (i += 1) {
    904         switch (categorizeStmt(ast, statements[i])) {
    905             .discard_identifier => continue,
    906             .trap_call => return i + 1 == statements.len,
    907             else => return false,
    908         }
    909     }
    910     return false;
    911 }
    912 
    913 const StmtCategory = enum {
    914     discard_undefined,
    915     discard_identifier,
    916     trap_call,
    917     other,
    918 };
    919 
    920 fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory {
    921     switch (ast.nodeTag(stmt)) {
    922         .builtin_call_two,
    923         .builtin_call_two_comma,
    924         .builtin_call,
    925         .builtin_call_comma,
    926         => {
    927             var buf: [2]Ast.Node.Index = undefined;
    928             const params = ast.builtinCallParams(&buf, stmt).?;
    929             return categorizeBuiltinCall(ast, ast.nodeMainToken(stmt), params);
    930         },
    931         .assign => {
    932             const lhs, const rhs = ast.nodeData(stmt).node_and_node;
    933             if (isDiscardIdent(ast, lhs) and ast.nodeTag(rhs) == .identifier) {
    934                 const name_bytes = ast.tokenSlice(ast.nodeMainToken(rhs));
    935                 if (std.mem.eql(u8, name_bytes, "undefined")) {
    936                     return .discard_undefined;
    937                 } else {
    938                     return .discard_identifier;
    939                 }
    940             }
    941             return .other;
    942         },
    943         else => return .other,
    944     }
    945 }
    946 
    947 fn categorizeBuiltinCall(
    948     ast: *const Ast,
    949     builtin_token: Ast.TokenIndex,
    950     params: []const Ast.Node.Index,
    951 ) StmtCategory {
    952     if (params.len != 0) return .other;
    953     const name_bytes = ast.tokenSlice(builtin_token);
    954     if (std.mem.eql(u8, name_bytes, "@trap"))
    955         return .trap_call;
    956     return .other;
    957 }
    958 
    959 fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool {
    960     return isMatchingIdent(ast, node, "_");
    961 }
    962 
    963 fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
    964     return isMatchingIdent(ast, node, "undefined");
    965 }
    966 
    967 fn isTrueIdent(ast: *const Ast, node: Ast.Node.Index) bool {
    968     return isMatchingIdent(ast, node, "true");
    969 }
    970 
    971 fn isFalseIdent(ast: *const Ast, node: Ast.Node.Index) bool {
    972     return isMatchingIdent(ast, node, "false");
    973 }
    974 
    975 fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {
    976     switch (ast.nodeTag(node)) {
    977         .identifier => {
    978             const token_index = ast.nodeMainToken(node);
    979             const name_bytes = ast.tokenSlice(token_index);
    980             return std.mem.eql(u8, name_bytes, string);
    981         },
    982         else => return false,
    983     }
    984 }
    985 
    986 fn isEmptyBlock(ast: *const Ast, node: Ast.Node.Index) bool {
    987     switch (ast.nodeTag(node)) {
    988         .block_two => {
    989             const opt_lhs, const opt_rhs = ast.nodeData(node).opt_node_and_opt_node;
    990             return opt_lhs == .none and opt_rhs == .none;
    991         },
    992         else => return false,
    993     }
    994 }