diff --git a/test/programs/pkg-shadow.flan b/test/programs/pkg-shadow.flan new file mode 100644 index 0000000..bbec4e5 --- /dev/null +++ b/test/programs/pkg-shadow.flan @@ -0,0 +1,17 @@ +;;;; A local shadowing an imported name (test/programs/shadow-pkg.flan). +;;;; +;;;; Nothing about this program is unusual; the package it imports is where the +;;;; shadowing is. What is asserted is that qualification stopped at the local: +;;;; 7 rather than the constant's 5, and 20 out of [assigned] with the +;;;; package's own [sink] still zero. A renamer that qualifies through a +;;;; binding produces no error anywhere — it silently reads and writes the +;;;; top-level name instead. + +(import shd "shadow-pkg.flan") + +(defn main [] i32 + (print (shd/shadowed)) (println "") + (print (shd/assigned)) (println "") + (print shd/sink) (println "") + (print shd/limit) (println "") + 0) diff --git a/test/programs/shadow-pkg.flan b/test/programs/shadow-pkg.flan new file mode 100644 index 0000000..7d49b58 --- /dev/null +++ b/test/programs/shadow-pkg.flan @@ -0,0 +1,21 @@ +;;;; A package whose own bodies bind locals named after its own top-level +;;;; names. Imported, every name this file owns is rewritten to shd/name +;;;; wherever it is *used* — and a local binding shadows, so inside these two +;;;; functions the bare name is the local and must be left alone. Get that +;;;; wrong and the code still compiles and still runs; it just reads a +;;;; different variable, which is why this needs a fixture and not a refusal. + +(defconst limit 5) +(defvar sink i32) + +;;; The expression case: the body's [limit] is the let's, not the constant. +(defn shadowed [] i32 + (let [limit 7] + limit)) + +;;; The place case, which is a separate line in the renamer: [set] takes a +;;; place, and a place that is a bare name has its own shadowing check. +(defn assigned [] i32 + (let [sink 0] + (set sink 20) + sink)) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index af19c20..4d044b6 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -660,6 +660,16 @@ let () = raylib itself. Loading it twice would declare every binding twice. *) outputs "a package reached along two routes" "programs/pkg-shared.flan" "ok\n"; + (* A local shadows an imported name. Qualification rewrites a package's own + names wherever they are used, and a binding is where it has to stop — + in an expression and in a place, which are two separate lines of the + renamer. Nothing refuses a renamer that qualifies through a binding: + the program builds and runs and reads the top-level name instead, so + what says it is wrong is the number. 7 is the let's and not the + constant's 5; 20 comes back out of [assigned] while the package's own + [sink] is still 0, which is the place half. *) + outputs "a local shadows an imported name" "programs/pkg-shadow.flan" + "7\n20\n0\n5\n"; (* Reach's walk, edge by edge. Pruning is what makes the link follow the program, and the cost of getting it wrong is not a wrong answer: a