# 0106 — namespaced-import internal names are silently bare-visible (over-permissive `isNameVisible`) > **RESOLVED** (flow stdlib/B attempt-2). Two coupled changes: > > 1. **Tightened bare visibility to the flat edge set.** `isNameVisible` / > `isCImportVisible` now route through the unified `isVisible` predicate over > `user_bare_flat` / `c_import_bare` (both join over `flat_import_graph`, not > `import_graph`). A namespaced-only import's internal name is no longer > bare-visible — face #1 now errors `'' is not visible; #import the > module that declares it`. > 2. **`#insert`-expansion visibility exemption.** The flat tightening alone broke > `std.print` / `log.*`: a library metaprogram's `#insert build_format(fmt)` > (comptime call) and `#insert "out(result);"` (inserted statement) expand in > the CALL SITE's `current_source_file`, so their bare names (`build_format`, > `out`, `emit`) were policed against the consumer's imports. Fix: a precise, > named exemption — `Lowering.in_insert_expansion` is set across > `lowerInsertExprValue` (the comptime eval + the parsed-back statements), and > `isNameVisible` / `isCImportVisible` fall open while it is set. This mirrors > the existing UFCS-alias / mangled-local "compiler indirection" exemptions; it > is NOT a blanket skip (it scopes to `#insert`-expanded code; ordinary bare > references are still policed). Library-internal call bodies (e.g. > `build_format`'s `concat` / `substr`) already resolve correctly — they lower > via `lowerFunctionBodyInto`, which pins `current_source_file` to the defining > module. > > Root cause: `isNameVisible` walked `import_graph` (flat AND namespaced edges) > where a bare name should join only over `flat_import_graph`. > Regression: `examples/0736-modules-namespaced-only-bare-not-visible.sx` (+ > `0736-…/a.sx`) — face #1 pinned (exit 1 + the stderr). Face #2 restored: > `examples/0015 / 0700 / 0718 / 1030` pass again (`run_examples` 471 → 472). > Fix in `src/ir/lower.zig` (`in_insert_expansion` + the two adapters) + > `src/ir/resolver.zig` (`VisibilityMode` modes, landed in attempt-1). **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) ```sx // m.sx secret :: () -> s64 { 7 } ``` ```sx // 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` — bare `secret` wrongly resolves to `m`'s author. - **Expected**: `error: 'secret' is not visible; #import the module that declares it` (it is reachable only as `m.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) ```sx // 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: ```sx 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 uses `import_graph` (flat **and** namespaced edges) where it should use `flat_import_graph` for a bare name. - Comptime / `#insert` expansion 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.zig` call 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's `current_source_file`. ## Investigation prompt (paste into a fresh session) > Fix issue 0106: `isNameVisible` over-permits bare references to a > namespaced-only import's internal names. Two coupled changes, in this order: > > 1. **Library-internal context.** Ensure a namespaced/comptime-expanded > function's body — including `#insert`ed statements and comptime calls like > `build_format` inside `std.print` — is visibility-checked in its **defining > module's** context, OR exempt compiler-generated / `#insert`ed bare calls > from the visibility check (mirror the UFCS-alias / mangled-name exemptions > at `src/ir/lower.zig` ~7284 and ~3237). Verify with the face-#2 repro and > examples `0015 / 0700 / 0718 / 1030`. > 2. **Tighten bare visibility to flat.** Change `isNameVisible` (and the > `c_import_bare` fall-through of `isCImportVisible`) to join over > `flat_import_graph` instead of `import_graph` — i.e. route them through the > resolver's `user_bare_flat` / `c_import_bare` modes (this is exactly Phase B > of the unified-resolver R5 plan; `src/ir/resolver.zig` already defines the > modes and `Lowering.visibleOverEdges` already takes a `.flat` / `.all` > selector). Verify the face-#1 repro now errors. > > Acceptance: the face-#1 repro errors ("not visible"); `bash tests/run_examples.sh` > is back to 471 `ok` with bare visibility on the flat edge set; add the face-#1 > repro as a pinned regression (`issues/0106-…` with an `expected/` marker, or > promote to `examples/07xx-modules-…`). Suspected files: `src/ir/lower.zig` > (visibility + comptime/#insert expansion context), `src/ir/resolver.zig` > (`user_bare_flat` / `c_import_bare`).