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>
This commit is contained in:
2026-02-19 21:34:39 +00:00
parent 03fb8ae879
commit e475c2d651
4 changed files with 35 additions and 17 deletions

View File

@@ -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 = &.{},

View File

@@ -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

View File

@@ -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)});

View File

@@ -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