Commit 91570cc42d

Adam Goertz <adambgoertz@gmail.com>
2023-11-05 00:00:28
zig-reduce: Reduce `if` expressions
Perform these transformations in this priority order: 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already. 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already. 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression. 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
1 parent 1b0b46a
Changed files (3)
lib
std
src
lib/std/zig/render.zig
@@ -25,7 +25,9 @@ pub const Fixups = struct {
     /// These global declarations will be omitted.
     omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},
     /// These expressions will be replaced with the string value.
-    replace_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .{},
+    replace_nodes_with_string: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .{},
+    /// These nodes will be replaced with a different node.
+    replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .{},
     /// Change all identifier names matching the key to be value instead.
     rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .{},
 
@@ -37,7 +39,8 @@ pub const Fixups = struct {
         return f.unused_var_decls.count() +
             f.gut_functions.count() +
             f.omit_nodes.count() +
-            f.replace_nodes.count() +
+            f.replace_nodes_with_string.count() +
+            f.replace_nodes_with_node.count() +
             f.rename_identifiers.count() +
             @intFromBool(f.rebase_imported_paths != null);
     }
@@ -46,7 +49,8 @@ pub const Fixups = struct {
         f.unused_var_decls.clearRetainingCapacity();
         f.gut_functions.clearRetainingCapacity();
         f.omit_nodes.clearRetainingCapacity();
-        f.replace_nodes.clearRetainingCapacity();
+        f.replace_nodes_with_string.clearRetainingCapacity();
+        f.replace_nodes_with_node.clearRetainingCapacity();
         f.rename_identifiers.clearRetainingCapacity();
 
         f.rebase_imported_paths = null;
@@ -56,7 +60,8 @@ pub const Fixups = struct {
         f.unused_var_decls.deinit(gpa);
         f.gut_functions.deinit(gpa);
         f.omit_nodes.deinit(gpa);
-        f.replace_nodes.deinit(gpa);
+        f.replace_nodes_with_string.deinit(gpa);
+        f.replace_nodes_with_node.deinit(gpa);
         f.rename_identifiers.deinit(gpa);
         f.* = undefined;
     }
@@ -329,10 +334,12 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
     const main_tokens = tree.nodes.items(.main_token);
     const node_tags = tree.nodes.items(.tag);
     const datas = tree.nodes.items(.data);
-    if (r.fixups.replace_nodes.get(node)) |replacement| {
+    if (r.fixups.replace_nodes_with_string.get(node)) |replacement| {
         try ais.writer().writeAll(replacement);
         try renderOnlySpace(r, space);
         return;
+    } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| {
+        return renderExpression(r, replacement, space);
     }
     switch (node_tags[node]) {
         .identifier => {
src/reduce/Walk.zig
@@ -27,6 +27,15 @@ pub const Transformation = union(enum) {
     },
     /// Replace an expression with `undefined`.
     replace_with_undef: Ast.Node.Index,
+    /// Replace an expression with `true`.
+    replace_with_true: Ast.Node.Index,
+    /// Replace an expression with `false`.
+    replace_with_false: Ast.Node.Index,
+    /// Replace a node with another node.
+    replace_node: struct {
+        to_replace: Ast.Node.Index,
+        replacement: Ast.Node.Index,
+    },
     /// Replace an `@import` with the imported file contents wrapped in a struct.
     inline_imported_file: InlineImportedFile,
 
@@ -558,7 +567,7 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
 
         .if_simple,
         .@"if",
-        => return walkIf(w, ast.fullIf(node).?),
+        => return walkIf(w, node, ast.fullIf(node).?),
 
         .asm_simple,
         .@"asm",
@@ -861,8 +870,6 @@ fn walkWhile(w: *Walk, while_node: Ast.full.While) Error!void {
         try walkExpression(w, while_node.ast.cont_expr);
     }
 
-    try walkExpression(w, while_node.ast.cond_expr); // condition
-
     if (while_node.ast.then_expr != 0) {
         try walkExpression(w, while_node.ast.then_expr);
     }
@@ -881,7 +888,37 @@ fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
     }
 }
 
-fn walkIf(w: *Walk, if_node: Ast.full.If) Error!void {
+fn walkIf(w: *Walk, node_index: Ast.Node.Index, if_node: Ast.full.If) Error!void {
+    assert(if_node.ast.cond_expr != 0);
+    assert(if_node.ast.then_expr != 0);
+
+    // Perform these transformations in this priority order:
+    // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
+    // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
+    // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
+    // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
+    if (!isTrueIdent(w.ast, if_node.ast.cond_expr) and
+        (if_node.ast.else_expr == 0 or isEmptyBlock(w.ast, if_node.ast.else_expr)))
+    {
+        try w.transformations.ensureUnusedCapacity(1);
+        w.transformations.appendAssumeCapacity(.{ .replace_with_true = if_node.ast.cond_expr });
+    } else if (!isFalseIdent(w.ast, if_node.ast.cond_expr) and isEmptyBlock(w.ast, if_node.ast.then_expr)) {
+        try w.transformations.ensureUnusedCapacity(1);
+        w.transformations.appendAssumeCapacity(.{ .replace_with_false = if_node.ast.cond_expr });
+    } else if (isTrueIdent(w.ast, if_node.ast.cond_expr)) {
+        try w.transformations.ensureUnusedCapacity(1);
+        w.transformations.appendAssumeCapacity(.{ .replace_node = .{
+            .to_replace = node_index,
+            .replacement = if_node.ast.then_expr,
+        } });
+    } else if (isFalseIdent(w.ast, if_node.ast.cond_expr)) {
+        try w.transformations.ensureUnusedCapacity(1);
+        w.transformations.appendAssumeCapacity(.{ .replace_node = .{
+            .to_replace = node_index,
+            .replacement = if_node.ast.else_expr,
+        } });
+    }
+
     try walkExpression(w, if_node.ast.cond_expr); // condition
 
     if (if_node.ast.then_expr != 0) {
@@ -1002,6 +1039,14 @@ fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
     return isMatchingIdent(ast, node, "undefined");
 }
 
+fn isTrueIdent(ast: *const Ast, node: Ast.Node.Index) bool {
+    return isMatchingIdent(ast, node, "true");
+}
+
+fn isFalseIdent(ast: *const Ast, node: Ast.Node.Index) bool {
+    return isMatchingIdent(ast, node, "false");
+}
+
 fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {
     const node_tags = ast.nodes.items(.tag);
     const main_tokens = ast.nodes.items(.main_token);
@@ -1014,3 +1059,14 @@ fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bo
         else => return false,
     }
 }
+
+fn isEmptyBlock(ast: *const Ast, node: Ast.Node.Index) bool {
+    const node_tags = ast.nodes.items(.tag);
+    const node_data = ast.nodes.items(.data);
+    switch (node_tags[node]) {
+        .block_two => {
+            return node_data[node].lhs == 0 and node_data[node].rhs == 0;
+        },
+        else => return false,
+    }
+}
src/reduce.zig
@@ -226,7 +226,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
             }
 
             try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
-            //std.debug.print("trying this code:\n{s}\n", .{rendered.items});
+            // std.debug.print("trying this code:\n{s}\n", .{rendered.items});
 
             const interestingness = try runCheck(arena, interestingness_argv.items);
             std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{
@@ -324,11 +324,20 @@ fn transformationsToFixups(
         .delete_var_decl => |delete_var_decl| {
             try fixups.omit_nodes.put(gpa, delete_var_decl.var_decl_node, {});
             for (delete_var_decl.references.items) |ident_node| {
-                try fixups.replace_nodes.put(gpa, ident_node, "undefined");
+                try fixups.replace_nodes_with_string.put(gpa, ident_node, "undefined");
             }
         },
         .replace_with_undef => |node| {
-            try fixups.replace_nodes.put(gpa, node, "undefined");
+            try fixups.replace_nodes_with_string.put(gpa, node, "undefined");
+        },
+        .replace_with_true => |node| {
+            try fixups.replace_nodes_with_string.put(gpa, node, "true");
+        },
+        .replace_with_false => |node| {
+            try fixups.replace_nodes_with_string.put(gpa, node, "false");
+        },
+        .replace_node => |r| {
+            try fixups.replace_nodes_with_node.put(gpa, r.to_replace, r.replacement);
         },
         .inline_imported_file => |inline_imported_file| {
             const full_imported_path = try std.fs.path.join(gpa, &.{
@@ -371,7 +380,7 @@ fn transformationsToFixups(
             try other_file_ast.renderToArrayList(&other_source, inlined_fixups);
             try other_source.appendSlice("}");
 
-            try fixups.replace_nodes.put(
+            try fixups.replace_nodes_with_string.put(
                 gpa,
                 inline_imported_file.builtin_call_node,
                 try arena.dupe(u8, other_source.items),