| author | |
| committer | |
| log | dc63426b1eaef9c65152c8e052e5552e593e9382 |
| tree | 4baa141bdd1da68a3f15164988317350f95b76c7 |
| parent | f24ceec35a6fd1e5e6a671461b78919b5f588a32 |
| parent | a9002156a09130038abcf418609fea725ce71bc2 |
| signature |
zig reduce: support inlining `@import` and more4 files changed, 433 insertions(+), 79 deletions(-)
lib/std/array_hash_map.zig+13| ... | ... | @@ -574,6 +574,19 @@ pub fn ArrayHashMapUnmanaged( |
| 574 | 574 | }; |
| 575 | 575 | } |
| 576 | 576 | |
| 577 | pub fn init(allocator: Allocator, key_list: []const K, value_list: []const V) !Self { | |
| 578 | var self: Self = .{}; | |
| 579 | try self.entries.resize(allocator, key_list.len); | |
| 580 | errdefer self.entries.deinit(allocator); | |
| 581 | @memcpy(self.keys(), key_list); | |
| 582 | if (@sizeOf(V) != 0) { | |
| 583 | assert(key_list.len == value_list.len); | |
| 584 | @memcpy(self.values(), value_list); | |
| 585 | } | |
| 586 | try self.reIndex(allocator); | |
| 587 | return self; | |
| 588 | } | |
| 589 | ||
| 577 | 590 | /// Frees the backing allocation and leaves the map in an undefined state. |
| 578 | 591 | /// Note that this does not free keys or values. You must take care of that |
| 579 | 592 | /// before calling this function, if it is needed. |
lib/std/zig/render.zig+114-20| ... | ... | @@ -24,14 +24,22 @@ pub const Fixups = struct { |
| 24 | 24 | gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{}, |
| 25 | 25 | /// These global declarations will be omitted. |
| 26 | 26 | omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{}, |
| 27 | /// These expressions will be replaced with `undefined`. | |
| 28 | replace_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{}, | |
| 27 | /// These expressions will be replaced with the string value. | |
| 28 | replace_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .{}, | |
| 29 | /// Change all identifier names matching the key to be value instead. | |
| 30 | rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .{}, | |
| 31 | ||
| 32 | /// All `@import` builtin calls which refer to a file path will be prefixed | |
| 33 | /// with this path. | |
| 34 | rebase_imported_paths: ?[]const u8 = null, | |
| 29 | 35 | |
| 30 | 36 | pub fn count(f: Fixups) usize { |
| 31 | 37 | return f.unused_var_decls.count() + |
| 32 | 38 | f.gut_functions.count() + |
| 33 | 39 | f.omit_nodes.count() + |
| 34 | f.replace_nodes.count(); | |
| 40 | f.replace_nodes.count() + | |
| 41 | f.rename_identifiers.count() + | |
| 42 | @intFromBool(f.rebase_imported_paths != null); | |
| 35 | 43 | } |
| 36 | 44 | |
| 37 | 45 | pub fn clearRetainingCapacity(f: *Fixups) void { |
| ... | ... | @@ -39,6 +47,9 @@ pub const Fixups = struct { |
| 39 | 47 | f.gut_functions.clearRetainingCapacity(); |
| 40 | 48 | f.omit_nodes.clearRetainingCapacity(); |
| 41 | 49 | f.replace_nodes.clearRetainingCapacity(); |
| 50 | f.rename_identifiers.clearRetainingCapacity(); | |
| 51 | ||
| 52 | f.rebase_imported_paths = null; | |
| 42 | 53 | } |
| 43 | 54 | |
| 44 | 55 | pub fn deinit(f: *Fixups, gpa: Allocator) void { |
| ... | ... | @@ -46,6 +57,7 @@ pub const Fixups = struct { |
| 46 | 57 | f.gut_functions.deinit(gpa); |
| 47 | 58 | f.omit_nodes.deinit(gpa); |
| 48 | 59 | f.replace_nodes.deinit(gpa); |
| 60 | f.rename_identifiers.deinit(gpa); | |
| 49 | 61 | f.* = undefined; |
| 50 | 62 | } |
| 51 | 63 | }; |
| ... | ... | @@ -121,6 +133,7 @@ fn renderMember( |
| 121 | 133 | ) Error!void { |
| 122 | 134 | const tree = r.tree; |
| 123 | 135 | const ais = r.ais; |
| 136 | const node_tags = tree.nodes.items(.tag); | |
| 124 | 137 | const token_tags = tree.tokens.items(.tag); |
| 125 | 138 | const main_tokens = tree.nodes.items(.main_token); |
| 126 | 139 | const datas = tree.nodes.items(.data); |
| ... | ... | @@ -182,6 +195,45 @@ fn renderMember( |
| 182 | 195 | ais.popIndent(); |
| 183 | 196 | try ais.insertNewline(); |
| 184 | 197 | try renderToken(r, tree.lastToken(body_node), space); // rbrace |
| 198 | } else if (r.fixups.unused_var_decls.count() != 0) { | |
| 199 | ais.pushIndentNextLine(); | |
| 200 | const lbrace = tree.nodes.items(.main_token)[body_node]; | |
| 201 | try renderToken(r, lbrace, .newline); | |
| 202 | ||
| 203 | var fn_proto_buf: [1]Ast.Node.Index = undefined; | |
| 204 | const full_fn_proto = tree.fullFnProto(&fn_proto_buf, fn_proto).?; | |
| 205 | var it = full_fn_proto.iterate(&tree); | |
| 206 | while (it.next()) |param| { | |
| 207 | const name_ident = param.name_token.?; | |
| 208 | assert(token_tags[name_ident] == .identifier); | |
| 209 | if (r.fixups.unused_var_decls.contains(name_ident)) { | |
| 210 | const w = ais.writer(); | |
| 211 | try w.writeAll("_ = "); | |
| 212 | try w.writeAll(tokenSliceForRender(r.tree, name_ident)); | |
| 213 | try w.writeAll(";\n"); | |
| 214 | } | |
| 215 | } | |
| 216 | var statements_buf: [2]Ast.Node.Index = undefined; | |
| 217 | const statements = switch (node_tags[body_node]) { | |
| 218 | .block_two, | |
| 219 | .block_two_semicolon, | |
| 220 | => b: { | |
| 221 | statements_buf = .{ datas[body_node].lhs, datas[body_node].rhs }; | |
| 222 | if (datas[body_node].lhs == 0) { | |
| 223 | break :b statements_buf[0..0]; | |
| 224 | } else if (datas[body_node].rhs == 0) { | |
| 225 | break :b statements_buf[0..1]; | |
| 226 | } else { | |
| 227 | break :b statements_buf[0..2]; | |
| 228 | } | |
| 229 | }, | |
| 230 | .block, | |
| 231 | .block_semicolon, | |
| 232 | => tree.extra_data[datas[body_node].lhs..datas[body_node].rhs], | |
| 233 | ||
| 234 | else => unreachable, | |
| 235 | }; | |
| 236 | return finishRenderBlock(r, body_node, statements, space); | |
| 185 | 237 | } else { |
| 186 | 238 | return renderExpression(r, body_node, space); |
| 187 | 239 | } |
| ... | ... | @@ -277,8 +329,8 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { |
| 277 | 329 | const main_tokens = tree.nodes.items(.main_token); |
| 278 | 330 | const node_tags = tree.nodes.items(.tag); |
| 279 | 331 | const datas = tree.nodes.items(.data); |
| 280 | if (r.fixups.replace_nodes.contains(node)) { | |
| 281 | try ais.writer().writeAll("undefined"); | |
| 332 | if (r.fixups.replace_nodes.get(node)) |replacement| { | |
| 333 | try ais.writer().writeAll(replacement); | |
| 282 | 334 | try renderOnlySpace(r, space); |
| 283 | 335 | return; |
| 284 | 336 | } |
| ... | ... | @@ -1057,7 +1109,7 @@ fn renderVarDecl( |
| 1057 | 1109 | space: Space, |
| 1058 | 1110 | ) Error!void { |
| 1059 | 1111 | try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space); |
| 1060 | if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token)) { | |
| 1112 | if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) { | |
| 1061 | 1113 | // Discard the variable like this: `_ = foo;` |
| 1062 | 1114 | const w = r.ais.writer(); |
| 1063 | 1115 | try w.writeAll("_ = "); |
| ... | ... | @@ -1515,6 +1567,7 @@ fn renderBuiltinCall( |
| 1515 | 1567 | const tree = r.tree; |
| 1516 | 1568 | const ais = r.ais; |
| 1517 | 1569 | const token_tags = tree.tokens.items(.tag); |
| 1570 | const main_tokens = tree.nodes.items(.main_token); | |
| 1518 | 1571 | |
| 1519 | 1572 | // TODO remove before release of 0.12.0 |
| 1520 | 1573 | const slice = tree.tokenSlice(builtin_token); |
| ... | ... | @@ -1609,6 +1662,26 @@ fn renderBuiltinCall( |
| 1609 | 1662 | return renderToken(r, builtin_token + 2, space); // ) |
| 1610 | 1663 | } |
| 1611 | 1664 | |
| 1665 | if (r.fixups.rebase_imported_paths) |prefix| { | |
| 1666 | if (mem.eql(u8, slice, "@import")) f: { | |
| 1667 | const param = params[0]; | |
| 1668 | const str_lit_token = main_tokens[param]; | |
| 1669 | assert(token_tags[str_lit_token] == .string_literal); | |
| 1670 | const token_bytes = tree.tokenSlice(str_lit_token); | |
| 1671 | const imported_string = std.zig.string_literal.parseAlloc(r.gpa, token_bytes) catch |err| switch (err) { | |
| 1672 | error.OutOfMemory => return error.OutOfMemory, | |
| 1673 | error.InvalidLiteral => break :f, | |
| 1674 | }; | |
| 1675 | defer r.gpa.free(imported_string); | |
| 1676 | const new_string = try std.fs.path.resolvePosix(r.gpa, &.{ prefix, imported_string }); | |
| 1677 | defer r.gpa.free(new_string); | |
| 1678 | ||
| 1679 | try renderToken(r, builtin_token + 1, .none); // ( | |
| 1680 | try ais.writer().print("\"{}\"", .{std.zig.fmtEscapes(new_string)}); | |
| 1681 | return renderToken(r, str_lit_token + 1, space); // ) | |
| 1682 | } | |
| 1683 | } | |
| 1684 | ||
| 1612 | 1685 | const last_param = params[params.len - 1]; |
| 1613 | 1686 | const after_last_param_token = tree.lastToken(last_param) + 1; |
| 1614 | 1687 | |
| ... | ... | @@ -1934,7 +2007,6 @@ fn renderBlock( |
| 1934 | 2007 | const tree = r.tree; |
| 1935 | 2008 | const ais = r.ais; |
| 1936 | 2009 | const token_tags = tree.tokens.items(.tag); |
| 1937 | const node_tags = tree.nodes.items(.tag); | |
| 1938 | 2010 | const lbrace = tree.nodes.items(.main_token)[block_node]; |
| 1939 | 2011 | |
| 1940 | 2012 | if (token_tags[lbrace - 1] == .colon and |
| ... | ... | @@ -1943,22 +2015,37 @@ fn renderBlock( |
| 1943 | 2015 | try renderIdentifier(r, lbrace - 2, .none, .eagerly_unquote); // identifier |
| 1944 | 2016 | try renderToken(r, lbrace - 1, .space); // : |
| 1945 | 2017 | } |
| 1946 | ||
| 1947 | 2018 | ais.pushIndentNextLine(); |
| 1948 | 2019 | if (statements.len == 0) { |
| 1949 | 2020 | try renderToken(r, lbrace, .none); |
| 1950 | } else { | |
| 1951 | try renderToken(r, lbrace, .newline); | |
| 1952 | for (statements, 0..) |stmt, i| { | |
| 1953 | if (i != 0) try renderExtraNewline(r, stmt); | |
| 1954 | switch (node_tags[stmt]) { | |
| 1955 | .global_var_decl, | |
| 1956 | .local_var_decl, | |
| 1957 | .simple_var_decl, | |
| 1958 | .aligned_var_decl, | |
| 1959 | => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon), | |
| 1960 | else => try renderExpression(r, stmt, .semicolon), | |
| 1961 | } | |
| 2021 | ais.popIndent(); | |
| 2022 | try renderToken(r, tree.lastToken(block_node), space); // rbrace | |
| 2023 | return; | |
| 2024 | } | |
| 2025 | try renderToken(r, lbrace, .newline); | |
| 2026 | return finishRenderBlock(r, block_node, statements, space); | |
| 2027 | } | |
| 2028 | ||
| 2029 | fn finishRenderBlock( | |
| 2030 | r: *Render, | |
| 2031 | block_node: Ast.Node.Index, | |
| 2032 | statements: []const Ast.Node.Index, | |
| 2033 | space: Space, | |
| 2034 | ) Error!void { | |
| 2035 | const tree = r.tree; | |
| 2036 | const node_tags = tree.nodes.items(.tag); | |
| 2037 | const ais = r.ais; | |
| 2038 | for (statements, 0..) |stmt, i| { | |
| 2039 | if (i != 0) try renderExtraNewline(r, stmt); | |
| 2040 | if (r.fixups.omit_nodes.contains(stmt)) continue; | |
| 2041 | switch (node_tags[stmt]) { | |
| 2042 | .global_var_decl, | |
| 2043 | .local_var_decl, | |
| 2044 | .simple_var_decl, | |
| 2045 | .aligned_var_decl, | |
| 2046 | => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon), | |
| 2047 | ||
| 2048 | else => try renderExpression(r, stmt, .semicolon), | |
| 1962 | 2049 | } |
| 1963 | 2050 | } |
| 1964 | 2051 | ais.popIndent(); |
| ... | ... | @@ -2809,6 +2896,13 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote |
| 2809 | 2896 | const token_tags = tree.tokens.items(.tag); |
| 2810 | 2897 | assert(token_tags[token_index] == .identifier); |
| 2811 | 2898 | const lexeme = tokenSliceForRender(tree, token_index); |
| 2899 | ||
| 2900 | if (r.fixups.rename_identifiers.get(lexeme)) |mangled| { | |
| 2901 | try r.ais.writer().writeAll(mangled); | |
| 2902 | try renderSpace(r, token_index, lexeme.len, space); | |
| 2903 | return; | |
| 2904 | } | |
| 2905 | ||
| 2812 | 2906 | if (lexeme[0] != '@') { |
| 2813 | 2907 | return renderToken(r, token_index, space); |
| 2814 | 2908 | } |
src/reduce.zig+144-20| ... | ... | @@ -5,6 +5,8 @@ const assert = std.debug.assert; |
| 5 | 5 | const fatal = @import("./main.zig").fatal; |
| 6 | 6 | const Ast = std.zig.Ast; |
| 7 | 7 | const Walk = @import("reduce/Walk.zig"); |
| 8 | const AstGen = @import("AstGen.zig"); | |
| 9 | const Zir = @import("Zir.zig"); | |
| 8 | 10 | |
| 9 | 11 | const usage = |
| 10 | 12 | \\zig reduce [options] ./checker root_source_file.zig [-- [argv]] |
| ... | ... | @@ -39,8 +41,6 @@ const Interestingness = enum { interesting, unknown, boring }; |
| 39 | 41 | // - add support for parsing the module flags |
| 40 | 42 | // - more fancy transformations |
| 41 | 43 | // - @import inlining of modules |
| 42 | // - @import inlining of files | |
| 43 | // - deleting unused functions and other globals | |
| 44 | 44 | // - removing statements or blocks of code |
| 45 | 45 | // - replacing operands of `and` and `or` with `true` and `false` |
| 46 | 46 | // - replacing if conditions with `true` and `false` |
| ... | ... | @@ -109,8 +109,14 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 109 | 109 | var rendered = std.ArrayList(u8).init(gpa); |
| 110 | 110 | defer rendered.deinit(); |
| 111 | 111 | |
| 112 | var tree = try parse(gpa, arena, root_source_file_path); | |
| 113 | defer tree.deinit(gpa); | |
| 112 | var astgen_input = std.ArrayList(u8).init(gpa); | |
| 113 | defer astgen_input.deinit(); | |
| 114 | ||
| 115 | var tree = try parse(gpa, root_source_file_path); | |
| 116 | defer { | |
| 117 | gpa.free(tree.source); | |
| 118 | tree.deinit(gpa); | |
| 119 | } | |
| 114 | 120 | |
| 115 | 121 | if (!skip_smoke_test) { |
| 116 | 122 | std.debug.print("smoke testing the interestingness check...\n", .{}); |
| ... | ... | @@ -126,6 +132,10 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 126 | 132 | |
| 127 | 133 | var fixups: Ast.Fixups = .{}; |
| 128 | 134 | defer fixups.deinit(gpa); |
| 135 | ||
| 136 | var more_fixups: Ast.Fixups = .{}; | |
| 137 | defer more_fixups.deinit(gpa); | |
| 138 | ||
| 129 | 139 | var rng = std.rand.DefaultPrng.init(seed); |
| 130 | 140 | |
| 131 | 141 | // 1. Walk the AST of the source file looking for independent |
| ... | ... | @@ -145,7 +155,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 145 | 155 | |
| 146 | 156 | var transformations = std.ArrayList(Walk.Transformation).init(gpa); |
| 147 | 157 | defer transformations.deinit(); |
| 148 | try Walk.findTransformations(&tree, &transformations); | |
| 158 | try Walk.findTransformations(arena, &tree, &transformations); | |
| 149 | 159 | sortTransformations(transformations.items, rng.random()); |
| 150 | 160 | |
| 151 | 161 | fresh: while (transformations.items.len > 0) { |
| ... | ... | @@ -156,29 +166,80 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 156 | 166 | var start_index: usize = 0; |
| 157 | 167 | |
| 158 | 168 | while (start_index < transformations.items.len) { |
| 159 | subset_size = @max(1, subset_size / 2); | |
| 169 | const prev_subset_size = subset_size; | |
| 170 | subset_size = @max(1, subset_size * 3 / 4); | |
| 171 | if (prev_subset_size > 1 and subset_size == 1) | |
| 172 | start_index = 0; | |
| 160 | 173 | |
| 161 | 174 | const this_set = transformations.items[start_index..][0..subset_size]; |
| 162 | try transformationsToFixups(gpa, this_set, &fixups); | |
| 175 | std.debug.print("trying {d} random transformations: ", .{subset_size}); | |
| 176 | for (this_set[0..@min(this_set.len, 20)]) |t| { | |
| 177 | std.debug.print("{s} ", .{@tagName(t)}); | |
| 178 | } | |
| 179 | std.debug.print("\n", .{}); | |
| 180 | try transformationsToFixups(gpa, arena, root_source_file_path, this_set, &fixups); | |
| 163 | 181 | |
| 164 | 182 | rendered.clearRetainingCapacity(); |
| 165 | 183 | try tree.renderToArrayList(&rendered, fixups); |
| 184 | ||
| 185 | // The transformations we applied may have resulted in unused locals, | |
| 186 | // in which case we would like to add the respective discards. | |
| 187 | { | |
| 188 | try astgen_input.resize(rendered.items.len); | |
| 189 | @memcpy(astgen_input.items, rendered.items); | |
| 190 | try astgen_input.append(0); | |
| 191 | const source_with_null = astgen_input.items[0 .. astgen_input.items.len - 1 :0]; | |
| 192 | var astgen_tree = try Ast.parse(gpa, source_with_null, .zig); | |
| 193 | defer astgen_tree.deinit(gpa); | |
| 194 | if (astgen_tree.errors.len != 0) { | |
| 195 | @panic("syntax errors occurred"); | |
| 196 | } | |
| 197 | var zir = try AstGen.generate(gpa, astgen_tree); | |
| 198 | defer zir.deinit(gpa); | |
| 199 | ||
| 200 | if (zir.hasCompileErrors()) { | |
| 201 | more_fixups.clearRetainingCapacity(); | |
| 202 | const payload_index = zir.extra[@intFromEnum(Zir.ExtraIndex.compile_errors)]; | |
| 203 | assert(payload_index != 0); | |
| 204 | const header = zir.extraData(Zir.Inst.CompileErrors, payload_index); | |
| 205 | var extra_index = header.end; | |
| 206 | for (0..header.data.items_len) |_| { | |
| 207 | const item = zir.extraData(Zir.Inst.CompileErrors.Item, extra_index); | |
| 208 | extra_index = item.end; | |
| 209 | const msg = zir.nullTerminatedString(item.data.msg); | |
| 210 | if (mem.eql(u8, msg, "unused local constant") or | |
| 211 | mem.eql(u8, msg, "unused local variable") or | |
| 212 | mem.eql(u8, msg, "unused function parameter") or | |
| 213 | mem.eql(u8, msg, "unused capture")) | |
| 214 | { | |
| 215 | const ident_token = item.data.token; | |
| 216 | try more_fixups.unused_var_decls.put(gpa, ident_token, {}); | |
| 217 | } else { | |
| 218 | std.debug.print("found other ZIR error: '{s}'\n", .{msg}); | |
| 219 | } | |
| 220 | } | |
| 221 | if (more_fixups.count() != 0) { | |
| 222 | rendered.clearRetainingCapacity(); | |
| 223 | try astgen_tree.renderToArrayList(&rendered, more_fixups); | |
| 224 | } | |
| 225 | } | |
| 226 | } | |
| 227 | ||
| 166 | 228 | try std.fs.cwd().writeFile(root_source_file_path, rendered.items); |
| 229 | //std.debug.print("trying this code:\n{s}\n", .{rendered.items}); | |
| 167 | 230 | |
| 168 | 231 | const interestingness = try runCheck(arena, interestingness_argv.items); |
| 169 | std.debug.print("{d} random transformations: {s}. {d} remaining\n", .{ | |
| 170 | subset_size, @tagName(interestingness), transformations.items.len - start_index, | |
| 232 | std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{ | |
| 233 | subset_size, @tagName(interestingness), start_index, transformations.items.len, | |
| 171 | 234 | }); |
| 172 | 235 | switch (interestingness) { |
| 173 | 236 | .interesting => { |
| 174 | const new_tree = try parse(gpa, arena, root_source_file_path); | |
| 237 | const new_tree = try parse(gpa, root_source_file_path); | |
| 238 | gpa.free(tree.source); | |
| 175 | 239 | tree.deinit(gpa); |
| 176 | 240 | tree = new_tree; |
| 177 | 241 | |
| 178 | try Walk.findTransformations(&tree, &transformations); | |
| 179 | // Resetting based on the seed again means we will get the same | |
| 180 | // results if restarting the reduction process from this new point. | |
| 181 | rng = std.rand.DefaultPrng.init(seed); | |
| 242 | try Walk.findTransformations(arena, &tree, &transformations); | |
| 182 | 243 | sortTransformations(transformations.items, rng.random()); |
| 183 | 244 | |
| 184 | 245 | continue :fresh; |
| ... | ... | @@ -188,6 +249,11 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 188 | 249 | // If we tested only one transformation, move on to the next one. |
| 189 | 250 | if (subset_size == 1) { |
| 190 | 251 | start_index += 1; |
| 252 | } else { | |
| 253 | start_index += subset_size; | |
| 254 | if (start_index + subset_size > transformations.items.len) { | |
| 255 | start_index = 0; | |
| 256 | } | |
| 191 | 257 | } |
| 192 | 258 | }, |
| 193 | 259 | } |
| ... | ... | @@ -241,6 +307,8 @@ fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness |
| 241 | 307 | |
| 242 | 308 | fn transformationsToFixups( |
| 243 | 309 | gpa: Allocator, |
| 310 | arena: Allocator, | |
| 311 | root_source_file_path: []const u8, | |
| 244 | 312 | transforms: []const Walk.Transformation, |
| 245 | 313 | fixups: *Ast.Fixups, |
| 246 | 314 | ) !void { |
| ... | ... | @@ -253,21 +321,77 @@ fn transformationsToFixups( |
| 253 | 321 | .delete_node => |decl_node| { |
| 254 | 322 | try fixups.omit_nodes.put(gpa, decl_node, {}); |
| 255 | 323 | }, |
| 324 | .delete_var_decl => |delete_var_decl| { | |
| 325 | try fixups.omit_nodes.put(gpa, delete_var_decl.var_decl_node, {}); | |
| 326 | for (delete_var_decl.references.items) |ident_node| { | |
| 327 | try fixups.replace_nodes.put(gpa, ident_node, "undefined"); | |
| 328 | } | |
| 329 | }, | |
| 256 | 330 | .replace_with_undef => |node| { |
| 257 | try fixups.replace_nodes.put(gpa, node, {}); | |
| 331 | try fixups.replace_nodes.put(gpa, node, "undefined"); | |
| 332 | }, | |
| 333 | .inline_imported_file => |inline_imported_file| { | |
| 334 | const full_imported_path = try std.fs.path.join(gpa, &.{ | |
| 335 | std.fs.path.dirname(root_source_file_path) orelse ".", | |
| 336 | inline_imported_file.imported_string, | |
| 337 | }); | |
| 338 | defer gpa.free(full_imported_path); | |
| 339 | var other_file_ast = try parse(gpa, full_imported_path); | |
| 340 | defer { | |
| 341 | gpa.free(other_file_ast.source); | |
| 342 | other_file_ast.deinit(gpa); | |
| 343 | } | |
| 344 | ||
| 345 | var inlined_fixups: Ast.Fixups = .{}; | |
| 346 | defer inlined_fixups.deinit(gpa); | |
| 347 | if (std.fs.path.dirname(inline_imported_file.imported_string)) |dirname| { | |
| 348 | inlined_fixups.rebase_imported_paths = dirname; | |
| 349 | } | |
| 350 | for (inline_imported_file.in_scope_names.keys()) |name| { | |
| 351 | // This name needs to be mangled in order to not cause an | |
| 352 | // ambiguous reference error. | |
| 353 | var i: u32 = 2; | |
| 354 | const mangled = while (true) : (i += 1) { | |
| 355 | const mangled = try std.fmt.allocPrint(gpa, "{s}{d}", .{ name, i }); | |
| 356 | if (!inline_imported_file.in_scope_names.contains(mangled)) | |
| 357 | break mangled; | |
| 358 | gpa.free(mangled); | |
| 359 | }; | |
| 360 | try inlined_fixups.rename_identifiers.put(gpa, name, mangled); | |
| 361 | } | |
| 362 | defer { | |
| 363 | for (inlined_fixups.rename_identifiers.values()) |v| { | |
| 364 | gpa.free(v); | |
| 365 | } | |
| 366 | } | |
| 367 | ||
| 368 | var other_source = std.ArrayList(u8).init(gpa); | |
| 369 | defer other_source.deinit(); | |
| 370 | try other_source.appendSlice("struct {\n"); | |
| 371 | try other_file_ast.renderToArrayList(&other_source, inlined_fixups); | |
| 372 | try other_source.appendSlice("}"); | |
| 373 | ||
| 374 | try fixups.replace_nodes.put( | |
| 375 | gpa, | |
| 376 | inline_imported_file.builtin_call_node, | |
| 377 | try arena.dupe(u8, other_source.items), | |
| 378 | ); | |
| 258 | 379 | }, |
| 259 | 380 | }; |
| 260 | 381 | } |
| 261 | 382 | |
| 262 | fn parse(gpa: Allocator, arena: Allocator, root_source_file_path: []const u8) !Ast { | |
| 263 | const source_code = try std.fs.cwd().readFileAllocOptions( | |
| 264 | arena, | |
| 265 | root_source_file_path, | |
| 383 | fn parse(gpa: Allocator, file_path: []const u8) !Ast { | |
| 384 | const source_code = std.fs.cwd().readFileAllocOptions( | |
| 385 | gpa, | |
| 386 | file_path, | |
| 266 | 387 | std.math.maxInt(u32), |
| 267 | 388 | null, |
| 268 | 389 | 1, |
| 269 | 390 | 0, |
| 270 | ); | |
| 391 | ) catch |err| { | |
| 392 | fatal("unable to open '{s}': {s}", .{ file_path, @errorName(err) }); | |
| 393 | }; | |
| 394 | errdefer gpa.free(source_code); | |
| 271 | 395 | |
| 272 | 396 | var tree = try Ast.parse(gpa, source_code, .zig); |
| 273 | 397 | errdefer tree.deinit(gpa); |
src/reduce/Walk.zig+162-39| ... | ... | @@ -2,11 +2,15 @@ const std = @import("std"); |
| 2 | 2 | const Ast = std.zig.Ast; |
| 3 | 3 | const Walk = @This(); |
| 4 | 4 | const assert = std.debug.assert; |
| 5 | const BuiltinFn = @import("../BuiltinFn.zig"); | |
| 5 | 6 | |
| 6 | 7 | ast: *const Ast, |
| 7 | 8 | transformations: *std.ArrayList(Transformation), |
| 8 | 9 | unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index), |
| 10 | in_scope_names: std.StringArrayHashMapUnmanaged(u32), | |
| 11 | replace_names: std.StringArrayHashMapUnmanaged(u32), | |
| 9 | 12 | gpa: std.mem.Allocator, |
| 13 | arena: std.mem.Allocator, | |
| 10 | 14 | |
| 11 | 15 | pub const Transformation = union(enum) { |
| 12 | 16 | /// Replace the fn decl AST Node with one whose body is only `@trap()` with |
| ... | ... | @@ -14,23 +18,51 @@ pub const Transformation = union(enum) { |
| 14 | 18 | gut_function: Ast.Node.Index, |
| 15 | 19 | /// Omit a global declaration. |
| 16 | 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 | }, | |
| 17 | 28 | /// Replace an expression with `undefined`. |
| 18 | 29 | replace_with_undef: Ast.Node.Index, |
| 30 | /// Replace an `@import` with the imported file contents wrapped in a struct. | |
| 31 | inline_imported_file: InlineImportedFile, | |
| 32 | ||
| 33 | pub const InlineImportedFile = struct { | |
| 34 | builtin_call_node: Ast.Node.Index, | |
| 35 | imported_string: []const u8, | |
| 36 | /// Identifier names that must be renamed in the inlined code or else | |
| 37 | /// will cause ambiguous reference errors. | |
| 38 | in_scope_names: std.StringArrayHashMapUnmanaged(void), | |
| 39 | }; | |
| 19 | 40 | }; |
| 20 | 41 | |
| 21 | 42 | pub const Error = error{OutOfMemory}; |
| 22 | 43 | |
| 23 | 44 | /// The result will be priority shuffled. |
| 24 | pub fn findTransformations(ast: *const Ast, transformations: *std.ArrayList(Transformation)) !void { | |
| 45 | pub fn findTransformations( | |
| 46 | arena: std.mem.Allocator, | |
| 47 | ast: *const Ast, | |
| 48 | transformations: *std.ArrayList(Transformation), | |
| 49 | ) !void { | |
| 25 | 50 | transformations.clearRetainingCapacity(); |
| 26 | 51 | |
| 27 | 52 | var walk: Walk = .{ |
| 28 | 53 | .ast = ast, |
| 29 | 54 | .transformations = transformations, |
| 30 | 55 | .gpa = transformations.allocator, |
| 56 | .arena = arena, | |
| 31 | 57 | .unreferenced_globals = .{}, |
| 58 | .in_scope_names = .{}, | |
| 59 | .replace_names = .{}, | |
| 32 | 60 | }; |
| 33 | defer walk.unreferenced_globals.deinit(walk.gpa); | |
| 61 | defer { | |
| 62 | walk.unreferenced_globals.deinit(walk.gpa); | |
| 63 | walk.in_scope_names.deinit(walk.gpa); | |
| 64 | walk.replace_names.deinit(walk.gpa); | |
| 65 | } | |
| 34 | 66 | |
| 35 | 67 | try walkMembers(&walk, walk.ast.rootDecls()); |
| 36 | 68 | |
| ... | ... | @@ -43,14 +75,18 @@ pub fn findTransformations(ast: *const Ast, transformations: *std.ArrayList(Tran |
| 43 | 75 | |
| 44 | 76 | fn walkMembers(w: *Walk, members: []const Ast.Node.Index) Error!void { |
| 45 | 77 | // First we scan for globals so that we can delete them while walking. |
| 46 | try scanDecls(w, members); | |
| 78 | try scanDecls(w, members, .add); | |
| 47 | 79 | |
| 48 | 80 | for (members) |member| { |
| 49 | 81 | try walkMember(w, member); |
| 50 | 82 | } |
| 83 | ||
| 84 | try scanDecls(w, members, .remove); | |
| 51 | 85 | } |
| 52 | 86 | |
| 53 | fn scanDecls(w: *Walk, members: []const Ast.Node.Index) Error!void { | |
| 87 | const ScanDeclsAction = enum { add, remove }; | |
| 88 | ||
| 89 | fn scanDecls(w: *Walk, members: []const Ast.Node.Index, action: ScanDeclsAction) Error!void { | |
| 54 | 90 | const ast = w.ast; |
| 55 | 91 | const gpa = w.gpa; |
| 56 | 92 | const node_tags = ast.nodes.items(.tag); |
| ... | ... | @@ -74,9 +110,27 @@ fn scanDecls(w: *Walk, members: []const Ast.Node.Index) Error!void { |
| 74 | 110 | |
| 75 | 111 | else => continue, |
| 76 | 112 | }; |
| 113 | ||
| 77 | 114 | assert(token_tags[name_token] == .identifier); |
| 78 | 115 | const name_bytes = ast.tokenSlice(name_token); |
| 79 | try w.unreferenced_globals.put(gpa, name_bytes, member_node); | |
| 116 | ||
| 117 | switch (action) { | |
| 118 | .add => { | |
| 119 | try w.unreferenced_globals.put(gpa, name_bytes, member_node); | |
| 120 | ||
| 121 | const gop = try w.in_scope_names.getOrPut(gpa, name_bytes); | |
| 122 | if (!gop.found_existing) gop.value_ptr.* = 0; | |
| 123 | gop.value_ptr.* += 1; | |
| 124 | }, | |
| 125 | .remove => { | |
| 126 | const entry = w.in_scope_names.getEntry(name_bytes).?; | |
| 127 | if (entry.value_ptr.* <= 1) { | |
| 128 | assert(w.in_scope_names.swapRemove(name_bytes)); | |
| 129 | } else { | |
| 130 | entry.value_ptr.* -= 1; | |
| 131 | } | |
| 132 | }, | |
| 133 | } | |
| 80 | 134 | } |
| 81 | 135 | } |
| 82 | 136 | |
| ... | ... | @@ -89,9 +143,10 @@ fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void { |
| 89 | 143 | try walkExpression(w, fn_proto); |
| 90 | 144 | const body_node = datas[decl].rhs; |
| 91 | 145 | if (!isFnBodyGutted(ast, body_node)) { |
| 146 | w.replace_names.clearRetainingCapacity(); | |
| 92 | 147 | try w.transformations.append(.{ .gut_function = decl }); |
| 148 | try walkExpression(w, body_node); | |
| 93 | 149 | } |
| 94 | try walkExpression(w, body_node); | |
| 95 | 150 | }, |
| 96 | 151 | .fn_proto_simple, |
| 97 | 152 | .fn_proto_multi, |
| ... | ... | @@ -121,7 +176,10 @@ fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void { |
| 121 | 176 | .container_field_init, |
| 122 | 177 | .container_field_align, |
| 123 | 178 | .container_field, |
| 124 | => try walkContainerField(w, ast.fullContainerField(decl).?), | |
| 179 | => { | |
| 180 | try w.transformations.append(.{ .delete_node = decl }); | |
| 181 | try walkContainerField(w, ast.fullContainerField(decl).?); | |
| 182 | }, | |
| 125 | 183 | |
| 126 | 184 | .@"comptime" => { |
| 127 | 185 | try w.transformations.append(.{ .delete_node = decl }); |
| ... | ... | @@ -140,7 +198,15 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void { |
| 140 | 198 | const node_tags = ast.nodes.items(.tag); |
| 141 | 199 | const datas = ast.nodes.items(.data); |
| 142 | 200 | switch (node_tags[node]) { |
| 143 | .identifier => try walkIdentifier(w, main_tokens[node]), | |
| 201 | .identifier => { | |
| 202 | const name_ident = main_tokens[node]; | |
| 203 | assert(token_tags[name_ident] == .identifier); | |
| 204 | const name_bytes = ast.tokenSlice(name_ident); | |
| 205 | _ = w.unreferenced_globals.swapRemove(name_bytes); | |
| 206 | if (w.replace_names.get(name_bytes)) |index| { | |
| 207 | try w.transformations.items[index].delete_var_decl.references.append(w.arena, node); | |
| 208 | } | |
| 209 | }, | |
| 144 | 210 | |
| 145 | 211 | .number_literal, |
| 146 | 212 | .char_literal, |
| ... | ... | @@ -437,16 +503,16 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void { |
| 437 | 503 | |
| 438 | 504 | .builtin_call_two, .builtin_call_two_comma => { |
| 439 | 505 | if (datas[node].lhs == 0) { |
| 440 | return walkBuiltinCall(w, main_tokens[node], &.{}); | |
| 506 | return walkBuiltinCall(w, node, &.{}); | |
| 441 | 507 | } else if (datas[node].rhs == 0) { |
| 442 | return walkBuiltinCall(w, main_tokens[node], &.{datas[node].lhs}); | |
| 508 | return walkBuiltinCall(w, node, &.{datas[node].lhs}); | |
| 443 | 509 | } else { |
| 444 | return walkBuiltinCall(w, main_tokens[node], &.{ datas[node].lhs, datas[node].rhs }); | |
| 510 | return walkBuiltinCall(w, node, &.{ datas[node].lhs, datas[node].rhs }); | |
| 445 | 511 | } |
| 446 | 512 | }, |
| 447 | 513 | .builtin_call, .builtin_call_comma => { |
| 448 | 514 | const params = ast.extra_data[datas[node].lhs..datas[node].rhs]; |
| 449 | return walkBuiltinCall(w, main_tokens[node], params); | |
| 515 | return walkBuiltinCall(w, node, params); | |
| 450 | 516 | }, |
| 451 | 517 | |
| 452 | 518 | .fn_proto_simple, |
| ... | ... | @@ -537,9 +603,12 @@ fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.Var |
| 537 | 603 | try walkExpression(w, var_decl.ast.section_node); |
| 538 | 604 | } |
| 539 | 605 | |
| 540 | assert(var_decl.ast.init_node != 0); | |
| 541 | ||
| 542 | return walkExpression(w, var_decl.ast.init_node); | |
| 606 | if (var_decl.ast.init_node != 0) { | |
| 607 | if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) { | |
| 608 | try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node }); | |
| 609 | } | |
| 610 | try walkExpression(w, var_decl.ast.init_node); | |
| 611 | } | |
| 543 | 612 | } |
| 544 | 613 | |
| 545 | 614 | fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void { |
| ... | ... | @@ -561,12 +630,12 @@ fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void { |
| 561 | 630 | try walkExpression(w, var_decl.ast.section_node); |
| 562 | 631 | } |
| 563 | 632 | |
| 564 | assert(var_decl.ast.init_node != 0); | |
| 565 | if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) { | |
| 566 | try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node }); | |
| 633 | if (var_decl.ast.init_node != 0) { | |
| 634 | if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) { | |
| 635 | try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node }); | |
| 636 | } | |
| 637 | try walkExpression(w, var_decl.ast.init_node); | |
| 567 | 638 | } |
| 568 | ||
| 569 | return walkExpression(w, var_decl.ast.init_node); | |
| 570 | 639 | } |
| 571 | 640 | |
| 572 | 641 | fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void { |
| ... | ... | @@ -576,7 +645,9 @@ fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void { |
| 576 | 645 | if (field.ast.align_expr != 0) { |
| 577 | 646 | try walkExpression(w, field.ast.align_expr); // alignment |
| 578 | 647 | } |
| 579 | try walkExpression(w, field.ast.value_expr); // value | |
| 648 | if (field.ast.value_expr != 0) { | |
| 649 | try walkExpression(w, field.ast.value_expr); // value | |
| 650 | } | |
| 580 | 651 | } |
| 581 | 652 | |
| 582 | 653 | fn walkBlock( |
| ... | ... | @@ -594,9 +665,34 @@ fn walkBlock( |
| 594 | 665 | .local_var_decl, |
| 595 | 666 | .simple_var_decl, |
| 596 | 667 | .aligned_var_decl, |
| 597 | => try walkLocalVarDecl(w, ast.fullVarDecl(stmt).?), | |
| 598 | ||
| 599 | else => try walkExpression(w, stmt), | |
| 668 | => { | |
| 669 | const var_decl = ast.fullVarDecl(stmt).?; | |
| 670 | if (var_decl.ast.init_node != 0 and | |
| 671 | isUndefinedIdent(w.ast, var_decl.ast.init_node)) | |
| 672 | { | |
| 673 | try w.transformations.append(.{ .delete_var_decl = .{ | |
| 674 | .var_decl_node = stmt, | |
| 675 | .references = .{}, | |
| 676 | } }); | |
| 677 | const name_tok = var_decl.ast.mut_token + 1; | |
| 678 | const name_bytes = ast.tokenSlice(name_tok); | |
| 679 | try w.replace_names.put(w.gpa, name_bytes, @intCast(w.transformations.items.len - 1)); | |
| 680 | } else { | |
| 681 | try walkLocalVarDecl(w, var_decl); | |
| 682 | } | |
| 683 | }, | |
| 684 | ||
| 685 | else => { | |
| 686 | switch (categorizeStmt(ast, stmt)) { | |
| 687 | // Don't try to remove `_ = foo;` discards; those are handled separately. | |
| 688 | .discard_identifier => {}, | |
| 689 | // definitely try to remove `_ = undefined;` though. | |
| 690 | .discard_undefined, .trap_call, .other => { | |
| 691 | try w.transformations.append(.{ .delete_node = stmt }); | |
| 692 | }, | |
| 693 | } | |
| 694 | try walkExpression(w, stmt); | |
| 695 | }, | |
| 600 | 696 | } |
| 601 | 697 | } |
| 602 | 698 | } |
| ... | ... | @@ -680,10 +776,35 @@ fn walkContainerDecl( |
| 680 | 776 | |
| 681 | 777 | fn walkBuiltinCall( |
| 682 | 778 | w: *Walk, |
| 683 | builtin_token: Ast.TokenIndex, | |
| 779 | call_node: Ast.Node.Index, | |
| 684 | 780 | params: []const Ast.Node.Index, |
| 685 | 781 | ) Error!void { |
| 686 | _ = builtin_token; | |
| 782 | const ast = w.ast; | |
| 783 | const main_tokens = ast.nodes.items(.main_token); | |
| 784 | const builtin_token = main_tokens[call_node]; | |
| 785 | const builtin_name = ast.tokenSlice(builtin_token); | |
| 786 | const info = BuiltinFn.list.get(builtin_name).?; | |
| 787 | switch (info.tag) { | |
| 788 | .import => { | |
| 789 | const operand_node = params[0]; | |
| 790 | const str_lit_token = main_tokens[operand_node]; | |
| 791 | const token_bytes = ast.tokenSlice(str_lit_token); | |
| 792 | if (std.mem.endsWith(u8, token_bytes, ".zig\"")) { | |
| 793 | const imported_string = std.zig.string_literal.parseAlloc(w.arena, token_bytes) catch | |
| 794 | unreachable; | |
| 795 | try w.transformations.append(.{ .inline_imported_file = .{ | |
| 796 | .builtin_call_node = call_node, | |
| 797 | .imported_string = imported_string, | |
| 798 | .in_scope_names = try std.StringArrayHashMapUnmanaged(void).init( | |
| 799 | w.arena, | |
| 800 | w.in_scope_names.keys(), | |
| 801 | &.{}, | |
| 802 | ), | |
| 803 | } }); | |
| 804 | } | |
| 805 | }, | |
| 806 | else => {}, | |
| 807 | } | |
| 687 | 808 | for (params) |param_node| { |
| 688 | 809 | try walkExpression(w, param_node); |
| 689 | 810 | } |
| ... | ... | @@ -821,6 +942,7 @@ fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool { |
| 821 | 942 | } |
| 822 | 943 | |
| 823 | 944 | const StmtCategory = enum { |
| 945 | discard_undefined, | |
| 824 | 946 | discard_identifier, |
| 825 | 947 | trap_call, |
| 826 | 948 | other, |
| ... | ... | @@ -846,8 +968,14 @@ fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory { |
| 846 | 968 | }, |
| 847 | 969 | .assign => { |
| 848 | 970 | const infix = datas[stmt]; |
| 849 | if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier) | |
| 850 | return .discard_identifier; | |
| 971 | if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier) { | |
| 972 | const name_bytes = ast.tokenSlice(main_tokens[infix.rhs]); | |
| 973 | if (std.mem.eql(u8, name_bytes, "undefined")) { | |
| 974 | return .discard_undefined; | |
| 975 | } else { | |
| 976 | return .discard_identifier; | |
| 977 | } | |
| 978 | } | |
| 851 | 979 | return .other; |
| 852 | 980 | }, |
| 853 | 981 | else => return .other, |
| ... | ... | @@ -867,26 +995,21 @@ fn categorizeBuiltinCall( |
| 867 | 995 | } |
| 868 | 996 | |
| 869 | 997 | fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool { |
| 870 | const node_tags = ast.nodes.items(.tag); | |
| 871 | const main_tokens = ast.nodes.items(.main_token); | |
| 872 | switch (node_tags[node]) { | |
| 873 | .identifier => { | |
| 874 | const token_index = main_tokens[node]; | |
| 875 | const name_bytes = ast.tokenSlice(token_index); | |
| 876 | return std.mem.eql(u8, name_bytes, "_"); | |
| 877 | }, | |
| 878 | else => return false, | |
| 879 | } | |
| 998 | return isMatchingIdent(ast, node, "_"); | |
| 880 | 999 | } |
| 881 | 1000 | |
| 882 | 1001 | fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool { |
| 1002 | return isMatchingIdent(ast, node, "undefined"); | |
| 1003 | } | |
| 1004 | ||
| 1005 | fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool { | |
| 883 | 1006 | const node_tags = ast.nodes.items(.tag); |
| 884 | 1007 | const main_tokens = ast.nodes.items(.main_token); |
| 885 | 1008 | switch (node_tags[node]) { |
| 886 | 1009 | .identifier => { |
| 887 | 1010 | const token_index = main_tokens[node]; |
| 888 | 1011 | const name_bytes = ast.tokenSlice(token_index); |
| 889 | return std.mem.eql(u8, name_bytes, "undefined"); | |
| 1012 | return std.mem.eql(u8, name_bytes, string); | |
| 890 | 1013 | }, |
| 891 | 1014 | else => return false, |
| 892 | 1015 | } |