Commit e13fc6b119

Andrew Kelley <andrew@ziglang.org>
2021-04-17 04:45:58
stage2: make `@import` relative to the current file
previously, it was incorrectly relative to the package directory
1 parent 415ef1b
src/Compilation.zig
@@ -1535,7 +1535,8 @@ pub fn update(self: *Compilation) !void {
 
             // Make sure std.zig is inside the import_table. We unconditionally need
             // it for start.zig.
-            _ = try module.importFile(module.root_pkg, "std");
+            const std_pkg = module.root_pkg.table.get("std").?;
+            _ = try module.importPkg(module.root_pkg, std_pkg);
 
             // Put a work item in for every known source file to detect if
             // it changed, and, if so, re-compute ZIR and then queue the job
@@ -2118,6 +2119,7 @@ fn workerAstGenFile(
                 // there is a missing `failed_files` error message.
                 error.OutOfMemory => {},
             };
+            return;
         },
     };
 
@@ -2136,7 +2138,7 @@ fn workerAstGenFile(
                 const lock = comp.mutex.acquire();
                 defer lock.release();
 
-                break :blk mod.importFile(file.pkg, import_path) catch continue;
+                break :blk mod.importFile(file, import_path) catch continue;
             };
             if (import_result.is_new) {
                 wg.start();
src/Module.zig
@@ -739,6 +739,7 @@ pub const Scope = struct {
         }
 
         pub fn deinit(file: *File, gpa: *Allocator) void {
+            gpa.free(file.sub_file_path);
             file.unload(gpa);
             file.* = undefined;
         }
@@ -746,6 +747,11 @@ pub const Scope = struct {
         pub fn getSource(file: *File, gpa: *Allocator) ![:0]const u8 {
             if (file.source_loaded) return file.source;
 
+            const root_dir_path = file.pkg.root_src_directory.path orelse ".";
+            log.debug("File.getSource, not cached. pkgdir={s} sub_file_path={s}", .{
+                root_dir_path, file.sub_file_path,
+            });
+
             // Keep track of inode, file size, mtime, hash so we can detect which files
             // have been modified when an incremental update is requested.
             var f = try file.pkg.root_src_directory.handle.openFile(file.sub_file_path, .{});
@@ -2633,20 +2639,70 @@ pub const ImportFileResult = struct {
     is_new: bool,
 };
 
+pub fn importPkg(mod: *Module, cur_pkg: *Package, pkg: *Package) !ImportFileResult {
+    const gpa = mod.gpa;
+
+    // The resolved path is used as the key in the import table, to detect if
+    // an import refers to the same as another, despite different relative paths
+    // or differently mapped package names.
+    const resolved_path = try std.fs.path.resolve(gpa, &[_][]const u8{
+        pkg.root_src_directory.path orelse ".", pkg.root_src_path,
+    });
+    var keep_resolved_path = false;
+    defer if (!keep_resolved_path) gpa.free(resolved_path);
+
+    const gop = try mod.import_table.getOrPut(gpa, resolved_path);
+    if (gop.found_existing) return ImportFileResult{
+        .file = gop.entry.value,
+        .is_new = false,
+    };
+    keep_resolved_path = true; // It's now owned by import_table.
+
+    const sub_file_path = try gpa.dupe(u8, pkg.root_src_path);
+    errdefer gpa.free(sub_file_path);
+
+    const new_file = try gpa.create(Scope.File);
+    errdefer gpa.destroy(new_file);
+
+    gop.entry.value = new_file;
+    new_file.* = .{
+        .sub_file_path = sub_file_path,
+        .source = undefined,
+        .source_loaded = false,
+        .tree_loaded = false,
+        .zir_loaded = false,
+        .stat_size = undefined,
+        .stat_inode = undefined,
+        .stat_mtime = undefined,
+        .tree = undefined,
+        .zir = undefined,
+        .status = .never_loaded,
+        .pkg = pkg,
+        .namespace = undefined,
+    };
+    return ImportFileResult{
+        .file = new_file,
+        .is_new = true,
+    };
+}
+
 pub fn importFile(
     mod: *Module,
-    cur_pkg: *Package,
+    cur_file: *Scope.File,
     import_string: []const u8,
 ) !ImportFileResult {
+    if (cur_file.pkg.table.get(import_string)) |pkg| {
+        return mod.importPkg(cur_file.pkg, pkg);
+    }
     const gpa = mod.gpa;
 
-    const cur_pkg_dir_path = cur_pkg.root_src_directory.path orelse ".";
-    const found_pkg = cur_pkg.table.get(import_string);
-
-    const resolved_path = if (found_pkg) |pkg|
-        try std.fs.path.resolve(gpa, &[_][]const u8{ pkg.root_src_directory.path orelse ".", pkg.root_src_path })
-    else
-        try std.fs.path.resolve(gpa, &[_][]const u8{ cur_pkg_dir_path, import_string });
+    // The resolved path is used as the key in the import table, to detect if
+    // an import refers to the same as another, despite different relative paths
+    // or differently mapped package names.
+    const cur_pkg_dir_path = cur_file.pkg.root_src_directory.path orelse ".";
+    const resolved_path = try std.fs.path.resolve(gpa, &[_][]const u8{
+        cur_pkg_dir_path, cur_file.sub_file_path, "..", import_string,
+    });
     var keep_resolved_path = false;
     defer if (!keep_resolved_path) gpa.free(resolved_path);
 
@@ -2655,20 +2711,28 @@ pub fn importFile(
         .file = gop.entry.value,
         .is_new = false,
     };
+    keep_resolved_path = true; // It's now owned by import_table.
 
-    if (found_pkg == null) {
-        const resolved_root_path = try std.fs.path.resolve(gpa, &[_][]const u8{cur_pkg_dir_path});
-        defer gpa.free(resolved_root_path);
+    const new_file = try gpa.create(Scope.File);
+    errdefer gpa.destroy(new_file);
 
-        if (!mem.startsWith(u8, resolved_path, resolved_root_path)) {
-            return error.ImportOutsidePkgPath;
-        }
+    const resolved_root_path = try std.fs.path.resolve(gpa, &[_][]const u8{cur_pkg_dir_path});
+    defer gpa.free(resolved_root_path);
+
+    if (!mem.startsWith(u8, resolved_path, resolved_root_path)) {
+        return error.ImportOutsidePkgPath;
     }
+    // +1 for the directory separator here.
+    const sub_file_path = try gpa.dupe(u8, resolved_path[resolved_root_path.len + 1 ..]);
+    errdefer gpa.free(sub_file_path);
+
+    log.debug("new importFile. resolved_root_path={s}, resolved_path={s}, sub_file_path={s}, import_string={s}", .{
+        resolved_root_path, resolved_path, sub_file_path, import_string,
+    });
 
-    const new_file = try gpa.create(Scope.File);
     gop.entry.value = new_file;
     new_file.* = .{
-        .sub_file_path = resolved_path,
+        .sub_file_path = sub_file_path,
         .source = undefined,
         .source_loaded = false,
         .tree_loaded = false,
@@ -2679,10 +2743,9 @@ pub fn importFile(
         .tree = undefined,
         .zir = undefined,
         .status = .never_loaded,
-        .pkg = found_pkg orelse cur_pkg,
+        .pkg = cur_file.pkg,
         .namespace = undefined,
     };
-    keep_resolved_path = true;
     return ImportFileResult{
         .file = new_file,
         .is_new = true,
src/Sema.zig
@@ -3904,7 +3904,7 @@ fn zirImport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!
     const src = inst_data.src();
     const operand = inst_data.get(sema.code);
 
-    const result = mod.importFile(block.getFileScope().pkg, operand) catch |err| switch (err) {
+    const result = mod.importFile(block.getFileScope(), operand) catch |err| switch (err) {
         error.ImportOutsidePkgPath => {
             return mod.fail(&block.base, src, "import of file outside package path: '{s}'", .{operand});
         },