Loading a package kept one table, keyed by real path, and used it for two different questions. Already loaded meant "skip", which is right for the second route of a diamond and wrong for a ring: a package that imported itself round a chain met its own entry, contributed nothing, and appeared to work. The comment said so and called it a feature. It is not one. A ring has no package order, and a definite package order is what the macro expander needs — every defmacro has to be compiled before anything that calls it. So the chain currently being read is now carried separately from the set already finished. A directory found in the first is a cycle and is refused; a directory found only in the second is still the diamond's second route and still a no-op. The refusal names the ring — a -> b -> c -> a — and only the ring, not the route that led to it. "There is a cycle" leaves the reader to find which three imports it was. pkgs now comes back dependencies-first, which is the topological order the acyclic rule buys. The declaration list is left alone: check.ml collects every top-level name before it checks any body, so declarations are order-independent by construction and sorting them would be churn in the field every test reads. The tests are a real tree rather than a second copy of pkg-shared. pkg-diamond builds a shape/Box inside area/ and hands it to a function declared inside draw/, which only type-checks if the bottom package was read once — two copies of one struct are two types. What proves it is the numbers, not the compile.
25 lines
1.1 KiB
Plaintext
25 lines
1.1 KiB
Plaintext
;;;; A diamond, with a type crossing it.
|
|
;;;;
|
|
;;;; The tree is pkg-diamond -> {area, draw} -> shape, which is the case that
|
|
;;;; only works if a package may import a package and if the shared bottom is
|
|
;;;; loaded once. The proof is not that it compiles but that a shape/Box built
|
|
;;;; inside area/ is accepted by a function declared inside draw/: two copies
|
|
;;;; of one struct would be two types, and passing one to the other would fail
|
|
;;;; to unify with a message naming shape/Box twice and explaining nothing.
|
|
;;;;
|
|
;;;; shape is never imported here directly. It arrives only through the two
|
|
;;;; middles, so its names are in scope because a package's import qualified
|
|
;;;; them, not because this file did — which is the inner-alias rule, visible.
|
|
|
|
(import area "pkgs/area")
|
|
(import draw "pkgs/draw")
|
|
|
|
(defn main [] i32
|
|
(let [b (area/unit)]
|
|
;; Built in area/, measured in draw/ — one type or no program.
|
|
(print (draw/describe b)) (println "")
|
|
(print (area/of b)) (println "")
|
|
;; And the bottom package's own constructor, reached through the chain.
|
|
(print (area/of (shape/box 4 5))) (println ""))
|
|
0)
|