Folds the coupled 0106 fix into Phase B. attempt-1 tightened the bare-name
visibility adapters (isNameVisible/isCImportVisible) to the flat_import_graph
edge set via the unified isVisible(.user_bare_flat/.c_import_bare) predicate;
that surfaced issue 0106 — std.print / log.* expand `#insert build_format(fmt)`
(comptime call) and `#insert "out(result);"` (inserted stmt) in the CONSUMER's
current_source_file, so their library-internal bare names were policed against
the consumer's imports and errored (run_examples 471 -> 467).
Fix: a precise, named exemption. Lowering.in_insert_expansion is set across
lowerInsertExprValue (the comptime eval + the parsed-back statements); the two
visibility adapters fall open while it is set — mirroring the existing
UFCS-alias / mangled-local "compiler indirection" exemptions. NOT a blanket
skip: scoped to #insert-expanded code, ordinary bare references stay policed.
Library-internal call bodies (build_format's concat/substr) already resolve in
the defining module — lowerFunctionBodyInto pins their current_source_file.
The flat tightening stays: a bare reference to a namespaced-only import's
internal name now correctly errors ('<name>' is not visible). This is the
Agra-ratified user-visible semantics change.
- face #1 pinned: examples/0736-modules-namespaced-only-bare-not-visible.sx
(+ a.sx) — exit 1 + stderr; fail-before (import_graph compiled it, exit 0) /
pass-after (flat set errors, exit 1).
- face #2 restored: examples 0015 / 0700 / 0718 / 1030 pass again.
- run_examples 471 -> 472 (the new regression).
- issues/0106 marked RESOLVED; readme.md documents namespaced-only visibility.
Collectors + unified predicate from attempt-1 (resolver.zig) unchanged; nothing
routes resolution AUTHOR-SELECTION through them yet (that is Phase C).
145 lines
7.3 KiB
Markdown
145 lines
7.3 KiB
Markdown
# 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 `'<name>' 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`).
|