commit e475c2d65191a1efbc1a984dc5f3a3f45d6dd68b (tree)
parent 03fb8ae879d79c606f2b4a85924540cb3b15ba64
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Thu, 19 Feb 2026 21:34:39 +0000
zig_compare_air: add module_root parameter to widen import resolution
Hardcoding the module root to dirname(src_path) caused "import of file
outside module path" for any @import("../...") in corpus files. Add an
optional module_root parameter so stages_test can symlink to the repo
root, allowing all relative imports to resolve within the module path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
4 files changed, 35 insertions(+), 17 deletions(-)
diff --git a/src/verbose_air.zig b/src/verbose_air.zig
@@ -142,11 +142,13 @@ const AirComparer = struct {
export fn zig_compare_air(
src_path_ptr: [*:0]const u8,
+ module_root_ptr: ?[*:0]const u8,
c_funcs_raw: ?*const anyopaque,
c_func_count: u32,
) CompareResult {
return zigCompareAirImpl(
std.mem.span(src_path_ptr),
+ if (module_root_ptr) |p| std.mem.span(p) else null,
@ptrCast(@alignCast(c_funcs_raw)),
c_func_count,
) catch |err| {
@@ -166,6 +168,7 @@ fn errResult(msg: []const u8) CompareResult {
fn zigCompareAirImpl(
src_path: []const u8,
+ module_root_opt: ?[]const u8,
c_funcs: ?[*]const CSemaFuncAir,
c_func_count: u32,
) !CompareResult {
@@ -210,14 +213,22 @@ fn zigCompareAirImpl(
// This avoids root mismatches in isNested checks, and also avoids resolving
// symlinks — the caller may use a directory symlink to keep the module root
// outside lib/std/ while preserving relative import resolution.
- const src_dir = std.fs.path.dirname(src_path) orelse ".";
- const src_basename = std.fs.path.basename(src_path);
+ const src_dir = module_root_opt orelse (std.fs.path.dirname(src_path) orelse ".");
+ const root_src_path = if (module_root_opt) |mr| blk: {
+ // src_path is "module_root/relative/path.zig" — strip module_root prefix
+ if (std.mem.startsWith(u8, src_path, mr)) {
+ var rest = src_path[mr.len..];
+ if (rest.len > 0 and rest[0] == std.fs.path.sep) rest = rest[1..];
+ break :blk rest;
+ }
+ break :blk std.fs.path.basename(src_path); // fallback
+ } else std.fs.path.basename(src_path);
const root_path = try Compilation.Path.fromUnresolved(arena, dirs, &.{src_dir});
const root_mod = try Package.Module.create(arena, .{
.paths = .{
.root = root_path,
- .root_src_path = src_basename,
+ .root_src_path = root_src_path,
},
.fully_qualified_name = "root",
.cc_argv = &.{},
diff --git a/stage0/dump.h b/stage0/dump.h
@@ -11,7 +11,7 @@ typedef struct {
} AirCompareResult;
// c_funcs: pointer to SemaFuncAir array (from sema.h). Passed as void* to avoid header dep.
-extern AirCompareResult zig_compare_air(const char* src_path, const void* c_funcs, uint32_t c_func_count);
+extern AirCompareResult zig_compare_air(const char* src_path, const char* module_root, const void* c_funcs, uint32_t c_func_count);
extern void zig_compare_result_free(AirCompareResult* result);
#endif
diff --git a/stage0/sema_test.zig b/stage0/sema_test.zig
@@ -12,7 +12,7 @@ const AirCompareResult = extern struct {
matched_count: u32,
error_msg: ?[*:0]u8,
};
-extern fn zig_compare_air([*:0]const u8, ?*const anyopaque, u32) AirCompareResult;
+extern fn zig_compare_air([*:0]const u8, ?[*:0]const u8, ?*const anyopaque, u32) AirCompareResult;
extern fn zig_compare_result_free(*AirCompareResult) void;
// Helper to convert C #define integer constants (c_int) to u32 for comparison
@@ -245,7 +245,7 @@ fn semaAirRawCheck(source: [:0]const u8) !void {
}
defer std.fs.cwd().deleteFile(tmp_path) catch {};
- var cmp_result = zig_compare_air(tmp_path, @ptrCast(result.c_func_air_list.items), result.c_func_air_list.len);
+ var cmp_result = zig_compare_air(tmp_path, null, @ptrCast(result.c_func_air_list.items), result.c_func_air_list.len);
defer zig_compare_result_free(&cmp_result);
if (cmp_result.error_msg) |e| {
std.debug.print("zig_compare_air error: {s}\n", .{std.mem.span(e)});
diff --git a/stage0/stages_test.zig b/stage0/stages_test.zig
@@ -13,7 +13,7 @@ const AirCompareResult = extern struct {
matched_count: u32,
error_msg: ?[*:0]u8,
};
-extern fn zig_compare_air([*:0]const u8, ?*const anyopaque, u32) AirCompareResult;
+extern fn zig_compare_air([*:0]const u8, ?[*:0]const u8, ?*const anyopaque, u32) AirCompareResult;
extern fn zig_compare_result_free(*AirCompareResult) void;
test "stages: corpus" {
@@ -71,22 +71,29 @@ fn stagesCheck(gpa: Allocator, comptime path: []const u8, source: [:0]const u8)
var c_func_air_list = sc.semaAnalyze(&c_sema);
defer sc.semaFuncAirListDeinit(&c_func_air_list);
- // Symlink the original file's directory to avoid module path conflicts
- // (source files inside lib/std/ conflict with the 'std' module)
- // while preserving relative import resolution for subdirectory imports.
+ // Symlink to the repo root so all relative imports resolve within
+ // the module root, while keeping logical paths under .zig-cache/tmp/
+ // to avoid 'std' module conflicts with lib/std/.
const this_dir = comptime std.fs.path.dirname(@src().file) orelse ".";
- const original_dir = comptime std.fs.path.dirname(this_dir ++ "/" ++ path) orelse ".";
const symlink_path = ".zig-cache/tmp/zig0_test";
- const abs_original_dir = std.fs.cwd().realpathAlloc(gpa, original_dir) catch return error.ResolvePath;
- defer gpa.free(abs_original_dir);
+ // All corpus paths start with "../"; strip to get repo-relative path.
+ const repo_relative = comptime blk: {
+ if (!std.mem.startsWith(u8, path, "../"))
+ @compileError("corpus path must start with '../'");
+ break :blk path["../".len..];
+ };
+
+ const abs_repo_root = std.fs.cwd().realpathAlloc(gpa, comptime this_dir ++ "/..") catch return error.ResolvePath;
+ defer gpa.free(abs_repo_root);
std.fs.cwd().deleteFile(symlink_path) catch {};
- std.fs.cwd().symLink(abs_original_dir, symlink_path, .{ .is_directory = true }) catch return error.SymlinkCreate;
+ std.fs.cwd().symLink(abs_repo_root, symlink_path, .{ .is_directory = true }) catch return error.SymlinkCreate;
defer std.fs.cwd().deleteFile(symlink_path) catch {};
- const test_src: [:0]const u8 = symlink_path ++ "/" ++ comptime std.fs.path.basename(path);
- var cmp_result = zig_compare_air(test_src.ptr, @ptrCast(c_func_air_list.items), c_func_air_list.len);
+ const test_src: [:0]const u8 = symlink_path ++ "/" ++ repo_relative;
+ const module_root: [:0]const u8 = symlink_path;
+ var cmp_result = zig_compare_air(test_src.ptr, module_root.ptr, @ptrCast(c_func_air_list.items), c_func_air_list.len);
defer zig_compare_result_free(&cmp_result);
if (cmp_result.error_msg) |e| {
std.debug.print("zig_compare_air error: {s}\n", .{std.mem.span(e)});
@@ -104,7 +111,7 @@ const last_successful_corpus = "../lib/std/crypto/codecs.zig";
// find ../{lib,src} -name '*.zig' | xargs -n1 stat -c "%s %n" | sort -n | awk '{printf " \""$2"\", // "$1"\n"}'
const corpus_files = .{
"../lib/std/crypto/codecs.zig", // 165
- //"../lib/std/os/uefi/tables/table_header.zig", // 214
+ "../lib/std/os/uefi/tables/table_header.zig", // 214
//"../lib/std/zig/llvm.zig", // 247
//"../lib/compiler_rt/neghf2.zig", // 265
//"../lib/compiler_rt/negxf2.zig", // 265