fix(imports): diagnose namespace-alias dup + propagate buildImportFacts errors [stdlib A attempt-2]
Two defects from the Phase A attempt-1 review.
F1 — duplicate-name diagnostic missed NAMESPACE ALIASES (silent first-win).
`addNamespace` unconditionally put the alias into scope/own_decls, so a
same-module collision between an authored decl and a `dup :: #import "…"`
alias compiled clean in the fn-then-alias order (the scalar
ModuleRawDeclIndex silently first-won). Now `addNamespace` returns a bool
and refuses a same-module duplicate (mirroring addOwnDecl); the call site
surfaces it via the new `reportDuplicateName` (the import_decl node has no
declName, so the alias name is passed explicitly). The C-import namespace
site gets the same guard. Both orders now emit "duplicate top-level
declaration 'X'" and exit nonzero (alias-then-fn was already caught by
addOwnDecl seeing the alias in scope).
F2 — buildImportFacts errors were swallowed by `else |_| {}` in core.zig
(REJECTED-PATTERN catch-all leaving the borrowed store silently empty).
`resolveImports` returns !void, so the call is now a plain `try` and a
build failure propagates instead of producing a stale/empty store.
Tests: extend the dup-name regression with fn-vs-namespace-alias
collisions in both orders. No resolution behavior change (no lower.zig
edits; run_examples 471 byte-identical); m3te ios-sim builds via the
worktree binary.
This commit is contained in:
@@ -357,6 +357,13 @@ pub const ResolvedModule = struct {
|
||||
/// Add another module as a namespaced import. The alias `name` becomes
|
||||
/// part of this module's own decls (so a flat-importer of this module
|
||||
/// sees the alias one hop out — matching authored names).
|
||||
///
|
||||
/// Returns `false` (and adds nothing) when `name` already names a decl
|
||||
/// authored in THIS module — symmetric to `addOwnDecl`, so a namespace
|
||||
/// alias colliding with a prior same-module name is dropped rather than
|
||||
/// shadowing it. The caller surfaces the drop via `reportDuplicateName`;
|
||||
/// the reverse order (alias first, then a same-name authored decl) is
|
||||
/// caught by `addOwnDecl` seeing the alias already in `scope`.
|
||||
pub fn addNamespace(
|
||||
self: *ResolvedModule,
|
||||
allocator: std.mem.Allocator,
|
||||
@@ -367,7 +374,8 @@ pub const ResolvedModule = struct {
|
||||
other: ResolvedModule,
|
||||
span: ast.Span,
|
||||
is_raw: bool,
|
||||
) !void {
|
||||
) !bool {
|
||||
if (self.scope.contains(name)) return false;
|
||||
const ns_node = try allocator.create(Node);
|
||||
ns_node.* = .{
|
||||
.span = span,
|
||||
@@ -391,6 +399,7 @@ pub const ResolvedModule = struct {
|
||||
try seen_list.put(name, {});
|
||||
try list.append(allocator, ns_node);
|
||||
try own_list.append(allocator, ns_node);
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn finalize(
|
||||
@@ -570,15 +579,24 @@ pub fn buildImportFacts(
|
||||
return .{ .decls = decls, .ns_edges = ns_edges };
|
||||
}
|
||||
|
||||
/// Surface a same-module duplicate top-level declaration as a hard error.
|
||||
/// `addOwnDecl` returns `false` when the name is already in this module's scope
|
||||
/// and drops the second author; without this the drop is silent, and the scalar
|
||||
/// `ModuleRawDeclIndex` would lose an authored name with no diagnostic.
|
||||
fn reportDuplicateDecl(diagnostics: ?*errors.DiagnosticList, added: bool, decl: *const Node) void {
|
||||
/// Surface a same-module duplicate top-level declaration as a hard error at an
|
||||
/// explicit name + span. `addOwnDecl` / `addNamespace` return `false` when the
|
||||
/// name is already in this module's scope and drop the second author; without
|
||||
/// this the drop is silent, and the scalar `ModuleRawDeclIndex` would lose an
|
||||
/// authored name with no diagnostic.
|
||||
fn reportDuplicateName(diagnostics: ?*errors.DiagnosticList, added: bool, name: []const u8, span: ast.Span) void {
|
||||
if (added) return;
|
||||
const diags = diagnostics orelse return;
|
||||
diags.addFmt(.err, span, "duplicate top-level declaration '{s}'", .{name});
|
||||
}
|
||||
|
||||
/// `reportDuplicateName` keyed off a node whose `declName()` carries the name
|
||||
/// (the regular authored-decl sites; an `import_decl` has no `declName`, so a
|
||||
/// namespace alias must use `reportDuplicateName` with the alias directly).
|
||||
fn reportDuplicateDecl(diagnostics: ?*errors.DiagnosticList, added: bool, decl: *const Node) void {
|
||||
if (added) return;
|
||||
const name = decl.data.declName() orelse return;
|
||||
diags.addFmt(.err, decl.span, "duplicate top-level declaration '{s}'", .{name});
|
||||
reportDuplicateName(diagnostics, added, name, decl.span);
|
||||
}
|
||||
|
||||
pub fn resolveImports(
|
||||
@@ -706,10 +724,14 @@ pub fn resolveImports(
|
||||
} },
|
||||
};
|
||||
ns_node.source_file = file_path;
|
||||
try mod.scope.put(ns_name, {});
|
||||
try seen_in_list.put(ns_name, {});
|
||||
try decl_list.append(allocator, ns_node);
|
||||
try own_decl_list.append(allocator, ns_node);
|
||||
if (mod.scope.contains(ns_name)) {
|
||||
reportDuplicateName(diagnostics, false, ns_name, decl.span);
|
||||
} else {
|
||||
try mod.scope.put(ns_name, {});
|
||||
try seen_in_list.put(ns_name, {});
|
||||
try decl_list.append(allocator, ns_node);
|
||||
try own_decl_list.append(allocator, ns_node);
|
||||
}
|
||||
} else {
|
||||
// Flat: add fn_decls directly + keep c_import_decl
|
||||
for (result.fn_decls) |fd| {
|
||||
@@ -796,7 +818,8 @@ pub fn resolveImports(
|
||||
};
|
||||
|
||||
if (imp.name) |ns_name| {
|
||||
try mod.addNamespace(allocator, &decl_list, &own_decl_list, &seen_in_list, ns_name, imported_mod, decl.span, imp.is_raw);
|
||||
const added = try mod.addNamespace(allocator, &decl_list, &own_decl_list, &seen_in_list, ns_name, imported_mod, decl.span, imp.is_raw);
|
||||
reportDuplicateName(diagnostics, added, ns_name, decl.span);
|
||||
} else {
|
||||
try mod.mergeFlat(allocator, &decl_list, &seen_in_list, &seen_nodes, imported_mod);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user