diff --git a/BUILT.md b/BUILT.md index 7f19e5e..4b59742 100644 --- a/BUILT.md +++ b/BUILT.md @@ -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 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 -is also what ends a cycle: a package that imports itself meets its own entry and contributes nothing the second time, -and the namespace being flat, mutually dependent packages simply work. The same directory under two *different* aliases -is refused. +one set of names or the checker sees every declaration twice. It is the one decision here that would be hardest to +change later, because a qualified name is an identity the `layout` op and the break loop both resolve by. + +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 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. 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