Say that a ring is refused, since the last note said the opposite

BUILT.md described a tolerated cycle as a property — "mutually dependent
packages simply work" — and NEXT.md still listed a package importing a package
as the real gap, which it stopped being some commits ago. Both now say what the
code does.

Written down with them: what a name imported through an intermediate package is
called, and why the inner alias is forced rather than chosen; that the diamond
is proven by the numbers pkg-diamond prints rather than by its compiling; and
that pkgs is topologically ordered while the declaration list deliberately is
not.

Package visibility stays on the list. The gap is that a package has no way to
mark a name private, which is surface syntax; the predicate and the refusal it
would hang off are already there.
This commit is contained in:
Joseph Ferano 2026-09-12 16:47:06 +07:00
parent c604911ecb
commit dc73b63446

View File

@ -346,10 +346,28 @@ all four. A file carries no `.c` and no `link` file; those belong to a directory
**A package may import a package.** The qualification flattens to the *inner* alias — raylib imported by a package that **A package may import a package.** The qualification flattens to the *inner* alias — raylib imported by a package that
is itself imported is still `rl/…`, never `sand/rl/…` — because a directory reached along two routes has to arrive under is itself imported is still `rl/…`, never `sand/rl/…` — because a directory reached along two routes has to arrive under
one set of names or the checker sees every declaration twice. A directory is keyed by its real path and read once, which one set of names or the checker sees every declaration twice. It is the one decision here that would be hardest to
is also what ends a cycle: a package that imports itself meets its own entry and contributes nothing the second time, change later, because a qualified name is an identity the `layout` op and the break loop both resolve by.
and the namespace being flat, mutually dependent packages simply work. The same directory under two *different* aliases
is refused. A directory is keyed by its real path and read once, so a **diamond** shares one copy of its bottom package.
`programs/pkg-diamond.flan` is the case that proves it at the level that matters: `area/` and `draw/` both import
`shape/`, a `shape/Box` is built inside `area/` and passed to a function declared inside `draw/`. Read `shape/` twice
and there are two structs both named `shape/Box` which do not unify, so what proves the dedupe is that the program
prints `3 6 20` and not that it compiles.
The same directory under two *different* aliases is refused, including when one of the two is a package's own import
and the other is pages away in the entry file — `programs/pkg-alias-clash.flan`.
**A ring is refused and named.** Earlier it was not: the read-once table swallowed the second arrival, so mutually
dependent packages appeared to work, and BUILT.md said so. What that cost is a definite package order, which is what
the macro expander needs — every `defmacro` has to be compiled before anything that calls it — so it is now a refusal
that names the ring, `a -> b -> c -> a`, rather than the single import that happened to close it. The two questions are
kept apart by two pieces of state: the chain currently being read, and the set already finished. Found in the first is
a cycle; found only in the second is the diamond's second route and still a no-op.
`Load.t.pkgs` comes back **dependencies first** — the topological order the acyclic rule buys. The declaration list is
deliberately unsorted: `check.ml` collects every top-level name in one pass before it checks any body, so top-level
names are order-independent by construction. The order exists for the expander, which cannot work that way.
**Visibility is one rule: `main` is not exported.** A package carrying one would collide with the importer's the moment **Visibility is one rule: `main` is not exported.** A package carrying one would collide with the importer's the moment
anything imported it, so a program could never be a package; and `main` is a reachability root, so an imported one would anything imported it, so a program could never be a package; and `main` is a reachability root, so an imported one would
@ -357,6 +375,9 @@ keep everything it calls alive. Writing `sand/main` is refused at the line that
checker it would be "unknown name", which is true and useless. checker it would be "unknown name", which is true and useless.
Still missing: a package-private marker for anything other than `main`, which is why `rl/get-color-raw` is callable. Still missing: a package-private marker for anything other than `main`, which is why `rl/get-color-raw` is callable.
The gap is surface syntax and not `load.ml``exported` is one predicate and the refusal machinery that points at the
line which tried already exists, so a second rule is a line. What does not exist is any way for a package to *mark* a
name private, and inventing one is a reader and parser change.
## The link follows the program ## The link follows the program