fix(imports): keep merged scope first-wins; index dups in module_fns only [0102a]

Attempt-1 retained a same-name cross-module FUNCTION author in the merged
decl list (mergeFlat + the directory merge), which is the list the existing
first-wins resolver consumes. That changed the data feeding resolution
(`mod.decls` carried two `greet`), violating this step's purpose: additive
indexes with ZERO resolution change.

Revert both merge sites to byte-for-byte first-wins, exactly as on
wt-fix-0102-base. The dropped same-name author is still retained — but only
in the SEPARATE `module_fns` index, which is built from each module's
`own_decls` (un-deduped, per-path) and which nothing reads yet. The
`flat_import_graph` side data is likewise untouched. Both are foundation
for fix-0102c's bare-name disambiguation; current resolution is unchanged.

Drop the now-unused `declAuthorsFn` helper (its only callers were the two
merge sites). `fnDeclOf` stays — it feeds the index.

Tests: the existing unit test now asserts the merged scope stays first-wins
(one `greet`, a.sx's author) while `module_fns` still retains BOTH authors
and `flat_import_graph` excludes the namespaced edge. Add a mixed non-fn/fn
collision test asserting the merged scope keeps a.sx's struct (first-wins),
unchanged by the function author.
This commit is contained in:
agra
2026-06-06 11:53:16 +03:00
parent ff9cb50079
commit 3dbc6f8434
2 changed files with 93 additions and 38 deletions

View File

@@ -5,24 +5,10 @@ const errors = @import("errors.zig");
const c_import = @import("c_import.zig");
const Node = ast.Node;
/// True iff `decl` authors a top-level FUNCTION — either a bare `fn_decl`
/// (`f :: (…) -> T { … }`) or a `const_decl` whose value is a function
/// (`f :: (…) { … }` parsed as a const-bound fn). Used by the flat / directory
/// merge to retain a same-name function authored by a DIFFERENT module instead
/// of first-wins dropping it (fix-0102a): a flat importer that pulls two modules
/// each authoring `f` needs BOTH decls reachable downstream. Non-function decls
/// keep first-wins dedup.
fn declAuthorsFn(decl: *const Node) bool {
return switch (decl.data) {
.fn_decl => true,
.const_decl => |cd| cd.value.data == .fn_decl,
else => false,
};
}
/// The `*const ast.FnDecl` a function-authoring decl carries, or null when the
/// decl is not a function (mirrors `declAuthorsFn`). Drives the per-module
/// `module_fns` identity index (fix-0102a).
/// decl is not a function — either a bare `fn_decl` (`f :: (…) -> T { … }`) or a
/// `const_decl` whose value is a function. Drives the per-module `module_fns`
/// identity index (fix-0102a).
fn fnDeclOf(decl: *const Node) ?*const ast.FnDecl {
return switch (decl.data) {
.fn_decl => &decl.data.fn_decl,
@@ -360,13 +346,8 @@ pub const ResolvedModule = struct {
for (other.decls) |decl| {
if (seen_nodes.contains(decl)) continue;
if (decl.data.declName()) |name| {
if (seen_list.contains(name)) {
// First-wins dedup for non-functions; retain a same-name
// FUNCTION authored by a different module (fix-0102a).
if (!declAuthorsFn(decl)) continue;
} else {
try seen_list.put(name, {});
}
if (seen_list.contains(name)) continue;
try seen_list.put(name, {});
}
try seen_nodes.put(decl, {});
try list.append(allocator, decl);
@@ -787,13 +768,8 @@ fn resolveDirectoryImport(
for (file_mod.decls) |decl| {
if (seen_nodes.contains(decl)) continue;
if (decl.data.declName()) |name| {
if (seen_in_list.contains(name)) {
// First-wins dedup for non-functions; retain a same-name
// FUNCTION authored by a different file (fix-0102a).
if (!declAuthorsFn(decl)) continue;
} else {
try seen_in_list.put(name, {});
}
if (seen_in_list.contains(name)) continue;
try seen_in_list.put(name, {});
}
try seen_nodes.put(decl, {});
try decl_list.append(allocator, decl);