Commit 8bd01d2d9b

Andrew Kelley <andrew@ziglang.org>
2023-11-04 21:57:29
zig reduce: add transformation for inlining file-based `@import`
One thing is missing for it to be useful, however, which is dealing with ambiguous reference errors introduced by the inlining process.
1 parent 98dc28b
Changed files (3)
lib
std
src
lib/std/zig/render.zig
@@ -24,8 +24,11 @@ pub const Fixups = struct {
     gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},
     /// These global declarations will be omitted.
     omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},
-    /// These expressions will be replaced with `undefined`.
-    replace_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) = .{},
+    /// All `@import` builtin calls which refer to a file path will be prefixed
+    /// with this path.
+    rebase_imported_paths: ?[]const u8 = null,
 
     pub fn count(f: Fixups) usize {
         return f.unused_var_decls.count() +
@@ -277,8 +280,8 @@ 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.contains(node)) {
-        try ais.writer().writeAll("undefined");
+    if (r.fixups.replace_nodes.get(node)) |replacement| {
+        try ais.writer().writeAll(replacement);
         try renderOnlySpace(r, space);
         return;
     }
@@ -1515,6 +1518,7 @@ fn renderBuiltinCall(
     const tree = r.tree;
     const ais = r.ais;
     const token_tags = tree.tokens.items(.tag);
+    const main_tokens = tree.nodes.items(.main_token);
 
     // TODO remove before release of 0.12.0
     const slice = tree.tokenSlice(builtin_token);
@@ -1609,6 +1613,26 @@ fn renderBuiltinCall(
         return renderToken(r, builtin_token + 2, space); // )
     }
 
+    if (r.fixups.rebase_imported_paths) |prefix| {
+        if (mem.eql(u8, slice, "@import")) f: {
+            const param = params[0];
+            const str_lit_token = main_tokens[param];
+            assert(token_tags[str_lit_token] == .string_literal);
+            const token_bytes = tree.tokenSlice(str_lit_token);
+            const imported_string = std.zig.string_literal.parseAlloc(r.gpa, token_bytes) catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.InvalidLiteral => break :f,
+            };
+            defer r.gpa.free(imported_string);
+            const new_string = try std.fs.path.resolvePosix(r.gpa, &.{ prefix, imported_string });
+            defer r.gpa.free(new_string);
+
+            try renderToken(r, builtin_token + 1, .none); // (
+            try ais.writer().print("\"{}\"", .{std.zig.fmtEscapes(new_string)});
+            return renderToken(r, str_lit_token + 1, space); // )
+        }
+    }
+
     const last_param = params[params.len - 1];
     const after_last_param_token = tree.lastToken(last_param) + 1;
 
src/reduce/Walk.zig
@@ -2,6 +2,7 @@ const std = @import("std");
 const Ast = std.zig.Ast;
 const Walk = @This();
 const assert = std.debug.assert;
+const BuiltinFn = @import("../BuiltinFn.zig");
 
 ast: *const Ast,
 transformations: *std.ArrayList(Transformation),
@@ -16,6 +17,11 @@ pub const Transformation = union(enum) {
     delete_node: Ast.Node.Index,
     /// Replace an expression with `undefined`.
     replace_with_undef: Ast.Node.Index,
+    /// Replace an `@import` with the imported file contents wrapped in a struct.
+    inline_imported_file: struct {
+        builtin_call_node: Ast.Node.Index,
+        imported_string: []const u8,
+    },
 };
 
 pub const Error = error{OutOfMemory};
@@ -437,16 +443,16 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
 
         .builtin_call_two, .builtin_call_two_comma => {
             if (datas[node].lhs == 0) {
-                return walkBuiltinCall(w, main_tokens[node], &.{});
+                return walkBuiltinCall(w, node, &.{});
             } else if (datas[node].rhs == 0) {
-                return walkBuiltinCall(w, main_tokens[node], &.{datas[node].lhs});
+                return walkBuiltinCall(w, node, &.{datas[node].lhs});
             } else {
-                return walkBuiltinCall(w, main_tokens[node], &.{ datas[node].lhs, datas[node].rhs });
+                return walkBuiltinCall(w, node, &.{ datas[node].lhs, datas[node].rhs });
             }
         },
         .builtin_call, .builtin_call_comma => {
             const params = ast.extra_data[datas[node].lhs..datas[node].rhs];
-            return walkBuiltinCall(w, main_tokens[node], params);
+            return walkBuiltinCall(w, node, params);
         },
 
         .fn_proto_simple,
@@ -680,10 +686,31 @@ fn walkContainerDecl(
 
 fn walkBuiltinCall(
     w: *Walk,
-    builtin_token: Ast.TokenIndex,
+    call_node: Ast.Node.Index,
     params: []const Ast.Node.Index,
 ) Error!void {
-    _ = builtin_token;
+    const ast = w.ast;
+    const gpa = w.gpa;
+    const main_tokens = ast.nodes.items(.main_token);
+    const builtin_token = main_tokens[call_node];
+    const builtin_name = ast.tokenSlice(builtin_token);
+    const info = BuiltinFn.list.get(builtin_name).?;
+    switch (info.tag) {
+        .import => {
+            const operand_node = params[0];
+            const str_lit_token = main_tokens[operand_node];
+            const token_bytes = ast.tokenSlice(str_lit_token);
+            const imported_string = std.zig.string_literal.parseAlloc(gpa, token_bytes) catch
+                unreachable;
+            if (std.mem.endsWith(u8, imported_string, ".zig")) {
+                try w.transformations.append(.{ .inline_imported_file = .{
+                    .builtin_call_node = call_node,
+                    .imported_string = imported_string,
+                } });
+            }
+        },
+        else => {},
+    }
     for (params) |param_node| {
         try walkExpression(w, param_node);
     }
src/reduce.zig
@@ -109,8 +109,11 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
     var rendered = std.ArrayList(u8).init(gpa);
     defer rendered.deinit();
 
-    var tree = try parse(gpa, arena, root_source_file_path);
-    defer tree.deinit(gpa);
+    var tree = try parse(gpa, root_source_file_path);
+    defer {
+        gpa.free(tree.source);
+        tree.deinit(gpa);
+    }
 
     if (!skip_smoke_test) {
         std.debug.print("smoke testing the interestingness check...\n", .{});
@@ -159,7 +162,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
             subset_size = @max(1, subset_size / 2);
 
             const this_set = transformations.items[start_index..][0..subset_size];
-            try transformationsToFixups(gpa, this_set, &fixups);
+            try transformationsToFixups(gpa, arena, root_source_file_path, this_set, &fixups);
 
             rendered.clearRetainingCapacity();
             try tree.renderToArrayList(&rendered, fixups);
@@ -171,7 +174,8 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
             });
             switch (interestingness) {
                 .interesting => {
-                    const new_tree = try parse(gpa, arena, root_source_file_path);
+                    const new_tree = try parse(gpa, root_source_file_path);
+                    gpa.free(tree.source);
                     tree.deinit(gpa);
                     tree = new_tree;
 
@@ -241,6 +245,8 @@ fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness
 
 fn transformationsToFixups(
     gpa: Allocator,
+    arena: Allocator,
+    root_source_file_path: []const u8,
     transforms: []const Walk.Transformation,
     fixups: *Ast.Fixups,
 ) !void {
@@ -254,20 +260,51 @@ fn transformationsToFixups(
             try fixups.omit_nodes.put(gpa, decl_node, {});
         },
         .replace_with_undef => |node| {
-            try fixups.replace_nodes.put(gpa, node, {});
+            try fixups.replace_nodes.put(gpa, node, "undefined");
+        },
+        .inline_imported_file => |inline_imported_file| {
+            defer gpa.free(inline_imported_file.imported_string);
+            const full_imported_path = try std.fs.path.join(gpa, &.{
+                std.fs.path.dirname(root_source_file_path) orelse ".",
+                inline_imported_file.imported_string,
+            });
+            defer gpa.free(full_imported_path);
+            var other_file_ast = try parse(gpa, full_imported_path);
+            defer {
+                gpa.free(other_file_ast.source);
+                other_file_ast.deinit(gpa);
+            }
+            var other_source = std.ArrayList(u8).init(gpa);
+            defer other_source.deinit();
+            var inlined_fixups: Ast.Fixups = .{};
+            defer inlined_fixups.deinit(gpa);
+            try other_source.appendSlice("struct {\n");
+            try other_file_ast.renderToArrayList(&other_source, .{
+                .rebase_imported_paths = std.fs.path.dirname(inline_imported_file.imported_string),
+            });
+            try other_source.appendSlice("}");
+
+            try fixups.replace_nodes.put(
+                gpa,
+                inline_imported_file.builtin_call_node,
+                try arena.dupe(u8, other_source.items),
+            );
         },
     };
 }
 
-fn parse(gpa: Allocator, arena: Allocator, root_source_file_path: []const u8) !Ast {
-    const source_code = try std.fs.cwd().readFileAllocOptions(
-        arena,
-        root_source_file_path,
+fn parse(gpa: Allocator, file_path: []const u8) !Ast {
+    const source_code = std.fs.cwd().readFileAllocOptions(
+        gpa,
+        file_path,
         std.math.maxInt(u32),
         null,
         1,
         0,
-    );
+    ) catch |err| {
+        fatal("unable to open '{s}': {s}", .{ file_path, @errorName(err) });
+    };
+    errdefer gpa.free(source_code);
 
     var tree = try Ast.parse(gpa, source_code, .zig);
     errdefer tree.deinit(gpa);