fix(C4): collectCImportSources recurses into nested namespaces

A named #import c unit declared inside an aliased module sits two
namespace levels deep in the merged tree; the one-level walk (the
extractLibraries/0130 pattern in c_import form) never collected it,
so the unit silently never compiled and its symbols resolved from
whatever process image carried the same names — surfaced by C4's
sqlite migration, where only the version pin could tell the OS copy
from the vendored one.
This commit is contained in:
agra
2026-06-12 17:16:03 +03:00
parent 44f4aab51c
commit e9b500a232
7 changed files with 54 additions and 33 deletions

View File

@@ -0,0 +1,11 @@
// A named `#import c` unit declared INSIDE an aliased module (so the
// unit's namespace nests two deep in the merged tree) still compiles
// and links — collectCImportSources recurses through namespaces, the
// same way #library collection does (issue 0130's pattern).
#import "modules/std.sx";
m :: #import "1623-cimport-unit-in-aliased-module/mod.sx";
main :: () -> i32 {
print("nested unit answer = {}\n", m.answer_via_mod());
0
}

View File

@@ -0,0 +1 @@
int unit_in_mod_answer(void) { return 54; }

View File

@@ -0,0 +1,10 @@
// A module that owns a named C unit + a hand-curated binding.
#import "modules/std.sx";
cunit :: #import c {
#source "inmod.c";
};
unit_in_mod_answer :: () -> i32 #foreign cunit "unit_in_mod_answer";
answer_via_mod :: () -> i32 { return unit_in_mod_answer(); }

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
nested unit answer = 54

View File

@@ -627,44 +627,41 @@ pub fn collectCImportSources(allocator: std.mem.Allocator, root: *const Node) ![
var seen = std.AutoHashMap([*]const []const u8, void).init(allocator);
defer seen.deinit();
for (root.data.root.decls) |decl| {
switch (decl.data) {
.c_import_decl => |ci| {
if (ci.sources.len > 0) {
const key = ci.sources.ptr;
if (!seen.contains(key)) {
try seen.put(key, {});
try infos.append(allocator, .{
.sources = ci.sources,
.includes = ci.includes,
.defines = ci.defines,
.flags = ci.flags,
});
}
}
},
.namespace_decl => |ns| {
for (ns.decls) |nd| {
if (nd.data == .c_import_decl) {
const nci = nd.data.c_import_decl;
if (nci.sources.len > 0) {
const key = nci.sources.ptr;
if (!seen.contains(key)) {
try seen.put(key, {});
try infos.append(allocator, .{
.sources = nci.sources,
.includes = nci.includes,
.defines = nci.defines,
.flags = nci.flags,
// Aliased imports lower to namespace_decl nodes and NEST when a
// namespaced module declares its own named unit — recurse, or a
// unit two aliases deep is silently never compiled and its symbols
// resolve from whatever process image carries the same names (the
// extractLibraries depth bug, issue 0130, in c_import form).
const walker = struct {
fn walk(
infos_: *std.ArrayList(CImportInfo),
seen_: *std.AutoHashMap([*]const []const u8, void),
alloc: std.mem.Allocator,
decls: []const *Node,
) !void {
for (decls) |d| {
switch (d.data) {
.c_import_decl => |ci| {
if (ci.sources.len > 0) {
const key = ci.sources.ptr;
if (!seen_.contains(key)) {
try seen_.put(key, {});
try infos_.append(alloc, .{
.sources = ci.sources,
.includes = ci.includes,
.defines = ci.defines,
.flags = ci.flags,
});
}
}
}
},
.namespace_decl => |ns| try walk(infos_, seen_, alloc, ns.decls),
else => {},
}
},
else => {},
}
}
}
};
try walker.walk(&infos, &seen, allocator, root.data.root.decls);
return try infos.toOwnedSlice(allocator);
}