Collectors (resolver.zig: collectVisibleAuthors/collectNamespaceAuthors + AuthorSet + VisibilityMode, 4 unit tests) + unified visibility predicate + isNameVisible/ isCImportVisible adapters routed to flat modes. Tightening surfaces issue 0106 (stdlib comptime expansion relies on the over-permissive import_graph join), so run_examples is 467/471 here. attempt-2 folds in the coupled comptime-context fix.
5.3 KiB
0106 — namespaced-import internal names are silently bare-visible (over-permissive isNameVisible)
Symptom. A bare reference to a top-level name authored in a module that the
consumer imports only namespaced (ns :: #import "m.sx") is silently
visible from the consumer. Observed: it compiles + runs. Expected: an error —
the name is reachable only as ns.name. Root: Lowering.isNameVisible walks
program_index.import_graph, which records BOTH flat (#import) and namespaced
(ns :: #import) edges; bare-name visibility should join only over FLAT edges
(flat_import_graph). This is the latent 0102-family visibility bug the Phase B
caller-mode audit (unified-resolver R5) was told to surface.
This directly gates flow stdlib/B: that step requires migrating
isNameVisible/isCImportVisible to the resolver's user_bare_flat/
c_import_bare modes (which walk flat_import_graph) byte-identically.
Switching the edge set drops run_examples from 471 → 467 (see face #2), so the
byte-identical requirement cannot hold until this bug is fixed.
Reproduction — face #1 (user-facing over-permissiveness)
// m.sx
secret :: () -> s64 { 7 }
// main.sx
m :: #import "m.sx";
main :: () -> s32 {
x := secret(); // bare; `secret` is only namespaced-imported as `m.secret`
0
}
- Observed (current master): compiles, runs, exit
0— baresecretwrongly resolves tom's author. - Expected:
error: 'secret' is not visible; #import the module that declares it(it is reachable only asm.secret).
(With the Phase-B edge-set change applied — isNameVisible over
flat_import_graph — this repro correctly errors, confirming the diagnosis.)
Reproduction — face #2 (library comptime entanglement: why a naive fix breaks std)
// main.sx
std :: #import "modules/std.sx";
main :: () -> s32 {
std.print("hello\n"); // legit qualified call
0
}
print in std.sx is a comptime metaprogram:
print :: ($fmt: string, ..$args) {
#insert build_format(fmt); // comptime call to a std-internal fn
#insert "out(result);"; // inserts a bare call to a std-internal fn
}
The comptime call to build_format and the inserted out(result) are bare
names authored in std.sx, but they are visibility-checked in the
consumer's current_source_file context (comptime / #insert expansion
happens at the call site). Today they pass only because import_graph[consumer]
contains the namespaced std edge. Tightening bare visibility to
flat_import_graph makes them error
('build_format' / 'out' is not visible). The same shape breaks log (emit).
Affected examples when the edge set is switched to flat:
0015-basic-demo, 0700-modules-import, 0718-modules-cli-exit-json,
1030-errors-log-and-comptime (467/471).
Root cause (suspected area)
Lowering.isNameVisible/isCImportVisible—src/ir/lower.zig(~1768-1840 after the Phase-B refactor;visibleOverEdges/nameVisibleOverEdges). The cross-module join usesimport_graph(flat and namespaced edges) where it should useflat_import_graphfor a bare name.- Comptime /
#insertexpansion context: the inserted/comptime-evaluated bare calls of a namespaced module's function are policed against the consumer's imports, not the defining module's own scope. The existing visibility check already exempts UFCS-alias rewrites and mangled local names as "compiler indirections" (lower.zigcall site ~7284, identifier site ~3237); inserted / comptime-generated bare calls are the same kind of indirection and are not yet exempt — or, equivalently, the expansion should restore the defining module'scurrent_source_file.
Investigation prompt (paste into a fresh session)
Fix issue 0106:
isNameVisibleover-permits bare references to a namespaced-only import's internal names. Two coupled changes, in this order:
- Library-internal context. Ensure a namespaced/comptime-expanded function's body — including
#inserted statements and comptime calls likebuild_formatinsidestd.print— is visibility-checked in its defining module's context, OR exempt compiler-generated /#inserted bare calls from the visibility check (mirror the UFCS-alias / mangled-name exemptions atsrc/ir/lower.zig~7284 and ~3237). Verify with the face-#2 repro and examples0015 / 0700 / 0718 / 1030.- Tighten bare visibility to flat. Change
isNameVisible(and thec_import_barefall-through ofisCImportVisible) to join overflat_import_graphinstead ofimport_graph— i.e. route them through the resolver'suser_bare_flat/c_import_baremodes (this is exactly Phase B of the unified-resolver R5 plan;src/ir/resolver.zigalready defines the modes andLowering.visibleOverEdgesalready takes a.flat/.allselector). Verify the face-#1 repro now errors.Acceptance: the face-#1 repro errors ("not visible");
bash tests/run_examples.shis back to 471okwith bare visibility on the flat edge set; add the face-#1 repro as a pinned regression (issues/0106-…with anexpected/marker, or promote toexamples/07xx-modules-…). Suspected files:src/ir/lower.zig(visibility + comptime/#insert expansion context),src/ir/resolver.zig(user_bare_flat/c_import_bare).