refactor(imports): retain dup same-name fn authors + build identity indexes [0102a]
First of four fix-0102 sub-steps. Purely additive: retains data that the flat/directory merge currently first-wins-drops and builds two identity indexes for later bare-name disambiguation (fix-0102c). No resolution change — the existing first-wins bare path still wins; suite unchanged. - mergeFlat + directory merge: stop dropping a same-name FUNCTION authored by a different module/file. Non-function decls keep first-wins dedup; node identity dedup is untouched. - flat_import_graph: a flat-only subset of import_graph, recording an edge only for a bare `#import` (imp.name == null), never a namespaced `ns :: #import`. Threaded through resolveImports/resolveDirectoryImport and into ProgramIndex. - module_fns (path -> name -> *const FnDecl): per-module authored-function index mirroring module_scopes, built in core.zig from the main module + cache. Same-name cross-module authors stay distinct under their own paths. - imports.test.zig: asserts both a.sx/b.sx greet authors are retained in module_fns and in the global flat list, and that flat_import_graph excludes the namespaced edge while import_graph includes it. Gate (this worktree): zig build, zig build test (398/398), bash tests/run_examples.sh (457 passed) all green.
This commit is contained in:
109
src/imports.test.zig
Normal file
109
src/imports.test.zig
Normal file
@@ -0,0 +1,109 @@
|
||||
// Tests for imports.zig — flat-import name-resolution data retention (fix-0102a).
|
||||
|
||||
const std = @import("std");
|
||||
const ast = @import("ast.zig");
|
||||
const parser = @import("parser.zig");
|
||||
const imports = @import("imports.zig");
|
||||
|
||||
var g_test_threaded: ?std.Io.Threaded = null;
|
||||
fn testIo() std.Io {
|
||||
if (g_test_threaded == null) {
|
||||
g_test_threaded = std.Io.Threaded.init(std.heap.page_allocator, .{});
|
||||
}
|
||||
return g_test_threaded.?.io();
|
||||
}
|
||||
|
||||
// Two flat-imported modules each author `greet`; a third is namespaced. The
|
||||
// step retains BOTH `greet` authors under their own paths in `module_fns`, and
|
||||
// records the namespaced import in `import_graph` but NOT in `flat_import_graph`.
|
||||
test "imports: module_fns retains same-name cross-module fns; flat_import_graph excludes namespaced edge" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
const io = testIo();
|
||||
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "greet :: () -> s64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "greet :: () -> s64 { 2 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "nsmod.sx", .data = "helper :: () -> s64 { 3 }\n" });
|
||||
const main_src =
|
||||
\\#import "a.sx";
|
||||
\\#import "b.sx";
|
||||
\\ns :: #import "nsmod.sx";
|
||||
\\main :: () -> s32 { 0 }
|
||||
\\
|
||||
;
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = main_src });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const dirlen = try tmp.dir.realPath(io, &dirbuf);
|
||||
const absdir = dirbuf[0..dirlen];
|
||||
|
||||
const main_path = try std.fmt.allocPrint(alloc, "{s}/main.sx", .{absdir});
|
||||
const a_path = try std.fmt.allocPrint(alloc, "{s}/a.sx", .{absdir});
|
||||
const b_path = try std.fmt.allocPrint(alloc, "{s}/b.sx", .{absdir});
|
||||
const ns_path = try std.fmt.allocPrint(alloc, "{s}/nsmod.sx", .{absdir});
|
||||
|
||||
const main_bytes = try std.Io.Dir.readFileAlloc(.cwd(), io, main_path, alloc, .limited(1 << 20));
|
||||
const main_source = try alloc.dupeZ(u8, main_bytes);
|
||||
var p = parser.Parser.init(alloc, main_source);
|
||||
const root = p.parse() catch return error.ParseFailed;
|
||||
|
||||
var chain = std.StringHashMap(void).init(alloc);
|
||||
var cache = imports.ModuleCache.init(alloc);
|
||||
var import_graph = std.StringHashMap(std.StringHashMap(void)).init(alloc);
|
||||
var flat_import_graph = std.StringHashMap(std.StringHashMap(void)).init(alloc);
|
||||
const stdlib_paths = [_][]const u8{};
|
||||
|
||||
const mod = try imports.resolveImports(
|
||||
alloc,
|
||||
io,
|
||||
root,
|
||||
absdir,
|
||||
main_path,
|
||||
&chain,
|
||||
&cache,
|
||||
null,
|
||||
null,
|
||||
&stdlib_paths,
|
||||
&import_graph,
|
||||
&flat_import_graph,
|
||||
.{},
|
||||
);
|
||||
|
||||
var module_fns = imports.ModuleFns.init(alloc);
|
||||
try imports.buildModuleFns(alloc, main_path, mod, &cache, &module_fns);
|
||||
|
||||
// mergeFlat no longer first-wins-drops the second `greet`: the global flat
|
||||
// decl list the lowering pass walks now carries BOTH authors.
|
||||
var greet_count: usize = 0;
|
||||
for (mod.decls) |decl| {
|
||||
const name = decl.data.declName() orelse continue;
|
||||
if (std.mem.eql(u8, name, "greet")) greet_count += 1;
|
||||
}
|
||||
try std.testing.expectEqual(@as(usize, 2), greet_count);
|
||||
|
||||
// module_fns retains BOTH authors of `greet`, keyed by their own paths.
|
||||
const a_fns = module_fns.get(a_path) orelse return error.MissingAFns;
|
||||
const b_fns = module_fns.get(b_path) orelse return error.MissingBFns;
|
||||
const a_greet = a_fns.get("greet") orelse return error.MissingAGreet;
|
||||
const b_greet = b_fns.get("greet") orelse return error.MissingBGreet;
|
||||
// Distinct authoring decls — not the same node deduped down to one.
|
||||
try std.testing.expect(a_greet != b_greet);
|
||||
|
||||
// flat_import_graph carries the two bare `#import` edges, NOT the
|
||||
// namespaced `ns :: #import` edge.
|
||||
const flat = flat_import_graph.get(main_path) orelse return error.MissingFlatEdges;
|
||||
try std.testing.expect(flat.contains(a_path));
|
||||
try std.testing.expect(flat.contains(b_path));
|
||||
try std.testing.expect(!flat.contains(ns_path));
|
||||
|
||||
// The full import_graph DOES record the namespaced edge (the contrast that
|
||||
// makes the flat-graph exclusion meaningful).
|
||||
const full = import_graph.get(main_path) orelse return error.MissingFullEdges;
|
||||
try std.testing.expect(full.contains(a_path));
|
||||
try std.testing.expect(full.contains(b_path));
|
||||
try std.testing.expect(full.contains(ns_path));
|
||||
}
|
||||
Reference in New Issue
Block a user