A form from a package file means what the import made it mean
C-c C-c on settle inside sand-sim/sim.flan declared settle, but the running program only ever knew it as sim/settle. The form spliced as a brand-new unrelated name, the evaluation answered ok, and nothing changed. Sand's simulation lives in a package, so the one thing worth tuning live was the one thing that silently did nothing - and reported success while doing it. Load now records what alias each package directory was imported under and what names it owns, because a file on disk does not say what it is called from outside; the importer chooses that. A session looks the editing file's directory up in that table and qualifies the incoming forms through Load's own qualify_decl, so a redefined settle lands on sim/settle and its call to move-grain lands on sim/move-grain, by the same rule the import used. A name the package does not own - the prelude's - is left alone. Derived from the path rather than sent by the editor, which is where this departs from CIDER's ns key: a Clojure namespace is declared in the file, but a Flan alias is not written anywhere the editor can see it. One directory imported under two aliases is refused with the reason instead of resolved to either.
This commit is contained in:
parent
52d3898116
commit
d9711f82bc
15
NEXT.md
15
NEXT.md
@ -573,6 +573,21 @@ Two things the session knows that no single evaluation could:
|
|||||||
the program uses, which is exactly where the silent version lives. The
|
the program uses, which is exactly where the silent version lives. The
|
||||||
fixtures carry an unused `defvar` and a C-called `defn` for that reason.
|
fixtures carry an unused `defvar` and a C-called `defn` for that reason.
|
||||||
|
|
||||||
|
**A form typed into a file that is imported as a package is qualified the way
|
||||||
|
the import qualified it.** `settle` in `sand-sim/sim.flan` becomes `sim/settle`,
|
||||||
|
and its call to `move-grain` becomes `sim/move-grain` — through `Load`'s own
|
||||||
|
`qualify_decl`, so the rule cannot drift from the one used at import time.
|
||||||
|
Without this the form spliced as a brand-new unrelated name: the evaluation
|
||||||
|
answered `ok`, and the running program went on calling the `sim/settle` it
|
||||||
|
already had. Since sand's simulation lives in a package, the one thing worth
|
||||||
|
tuning live was the one thing that silently did nothing.
|
||||||
|
|
||||||
|
It is derived from the file's path and **not sent by the editor**, which is
|
||||||
|
where this departs from CIDER's `ns` key: a Clojure namespace is declared in
|
||||||
|
the file, but a Flan alias is chosen by whatever imported the directory and is
|
||||||
|
written nowhere the editor can see. One directory imported under two aliases is
|
||||||
|
refused with the reason rather than resolved to either.
|
||||||
|
|
||||||
The accumulated list is the **post-`Load`** one, so an evaluated `(import …)`
|
The accumulated list is the **post-`Load`** one, so an evaluated `(import …)`
|
||||||
is spliced as its expansion. Otherwise re-evaluating a file that imports
|
is spliced as its expansion. Otherwise re-evaluating a file that imports
|
||||||
something appends a second import, `Load` expands it again, and the
|
something appends a second import, `Load` expands it again, and the
|
||||||
|
|||||||
16
lib/load.ml
16
lib/load.ml
@ -30,8 +30,16 @@ type t = {
|
|||||||
decls : Ast.decl list;
|
decls : Ast.decl list;
|
||||||
csrcs : string list; (* C sources compiled into the build *)
|
csrcs : string list; (* C sources compiled into the build *)
|
||||||
lflags : string list; (* extra linker arguments *)
|
lflags : string list; (* extra linker arguments *)
|
||||||
|
(* Which alias each package's directory was imported under, and the names it
|
||||||
|
owns. A file on disk does not say what it is called from outside — the
|
||||||
|
*importer* chooses that — so this is the only place the answer exists, and
|
||||||
|
a REPL editing a package's source needs it to know that [settle] typed in
|
||||||
|
sand-sim/sim.flan means [sim/settle] to the running program. *)
|
||||||
|
pkgs : pkg list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
and pkg = { alias : string; dir : string; owns : string list }
|
||||||
|
|
||||||
let fail loc fmt = Printf.ksprintf (fun m -> raise (Loc.Error (loc, m))) fmt
|
let fail loc fmt = Printf.ksprintf (fun m -> raise (Loc.Error (loc, m))) fmt
|
||||||
|
|
||||||
(* "vendor:raylib" -> the collection "vendor" and the subpath "raylib". A path
|
(* "vendor:raylib" -> the collection "vendor" and the subpath "raylib". A path
|
||||||
@ -252,7 +260,8 @@ let import ~loc alias dir =
|
|||||||
close_in ch; r
|
close_in ch; r
|
||||||
end
|
end
|
||||||
in
|
in
|
||||||
{ decls; csrcs = entries dir ".c"; lflags }
|
{ decls; csrcs = entries dir ".c"; lflags;
|
||||||
|
pkgs = [ { alias; dir; owns = owned } ] }
|
||||||
|
|
||||||
(* ── The one entry point ───────────────────────────────────────────── *)
|
(* ── The one entry point ───────────────────────────────────────────── *)
|
||||||
|
|
||||||
@ -265,7 +274,8 @@ let program ~file (decls : Ast.decl list) : t =
|
|||||||
let p = import ~loc:d.Ast.dloc alias dir in
|
let p = import ~loc:d.Ast.dloc alias dir in
|
||||||
{ decls = acc.decls @ p.decls;
|
{ decls = acc.decls @ p.decls;
|
||||||
csrcs = acc.csrcs @ p.csrcs;
|
csrcs = acc.csrcs @ p.csrcs;
|
||||||
lflags = acc.lflags @ p.lflags }
|
lflags = acc.lflags @ p.lflags;
|
||||||
|
pkgs = acc.pkgs @ p.pkgs }
|
||||||
| _ -> { acc with decls = acc.decls @ [ d ] })
|
| _ -> { acc with decls = acc.decls @ [ d ] })
|
||||||
{ decls = []; csrcs = []; lflags = [] }
|
{ decls = []; csrcs = []; lflags = []; pkgs = [] }
|
||||||
decls
|
decls
|
||||||
|
|||||||
@ -33,6 +33,7 @@ type t = {
|
|||||||
mutable decls : Ast.decl list; (* post-Load: flat, one namespace *)
|
mutable decls : Ast.decl list; (* post-Load: flat, one namespace *)
|
||||||
mutable program : Tast.program; (* the last thing that checked *)
|
mutable program : Tast.program; (* the last thing that checked *)
|
||||||
host : Tast.program; (* what the process was built from *)
|
host : Tast.program; (* what the process was built from *)
|
||||||
|
pkgs : Load.pkg list; (* alias, directory, names owned *)
|
||||||
}
|
}
|
||||||
|
|
||||||
let fail = Loc.fail
|
let fail = Loc.fail
|
||||||
@ -59,7 +60,44 @@ let rec same_const (a : Tast.expr) (b : Tast.expr) =
|
|||||||
let create ~file =
|
let create ~file =
|
||||||
let l = Load.program ~file (Parse.program (Reader.read_file file)) in
|
let l = Load.program ~file (Parse.program (Reader.read_file file)) in
|
||||||
let p = Check.program l.Load.decls in
|
let p = Check.program l.Load.decls in
|
||||||
({ file; decls = l.Load.decls; program = p; host = p }, l)
|
({ file; decls = l.Load.decls; program = p; host = p; pkgs = l.Load.pkgs }, l)
|
||||||
|
|
||||||
|
(* Which package a file being edited belongs to, if any.
|
||||||
|
|
||||||
|
A form typed into sand-sim/sim.flan declares [settle], but the running
|
||||||
|
program only ever knew it as [sim/settle]: the alias is chosen by whatever
|
||||||
|
imported the directory, and is written nowhere in the file itself. Without
|
||||||
|
this the form splices as a brand-new unrelated name, the evaluation reports
|
||||||
|
success, and nothing changes — the exact failure this whole design is meant
|
||||||
|
to make impossible.
|
||||||
|
|
||||||
|
Derived from the path rather than sent by the editor for that same reason:
|
||||||
|
the editor cannot know an alias the file does not mention. *)
|
||||||
|
let package_of t origin =
|
||||||
|
match origin with
|
||||||
|
| "" -> None
|
||||||
|
| origin ->
|
||||||
|
let dir =
|
||||||
|
try Filename.dirname (Unix.realpath origin)
|
||||||
|
with Unix.Unix_error _ -> Filename.dirname origin
|
||||||
|
in
|
||||||
|
let same p =
|
||||||
|
let d =
|
||||||
|
try Unix.realpath p.Load.dir with Unix.Unix_error _ -> p.Load.dir
|
||||||
|
in
|
||||||
|
String.equal d dir
|
||||||
|
in
|
||||||
|
(match List.filter same t.pkgs with
|
||||||
|
| [] -> None
|
||||||
|
| [ p ] -> Some p
|
||||||
|
(* One directory under two aliases: both are live in the program and a
|
||||||
|
form cannot mean both. Say so rather than picking one. *)
|
||||||
|
| ps ->
|
||||||
|
Loc.fail Loc.unknown
|
||||||
|
"%s is imported under more than one alias (%s); a form here would \
|
||||||
|
have to mean all of them"
|
||||||
|
dir
|
||||||
|
(String.concat ", " (List.map (fun p -> p.Load.alias) ps)))
|
||||||
|
|
||||||
(* A name the running process exports. Everything else is looked up by name at
|
(* A name the running process exports. Everything else is looked up by name at
|
||||||
install time — see [Emit.redefinition]'s [known]. *)
|
install time — see [Emit.redefinition]'s [known]. *)
|
||||||
@ -207,7 +245,22 @@ let eval ?(origin = "<eval>") t src : change =
|
|||||||
why the accumulated list is the post-Load one: re-evaluating a file that
|
why the accumulated list is the post-Load one: re-evaluating a file that
|
||||||
imports something would otherwise append a second copy of the import and
|
imports something would otherwise append a second copy of the import and
|
||||||
the duplicate-name pass would reject it. *)
|
the duplicate-name pass would reject it. *)
|
||||||
let incoming = (Load.program ~file:t.file (Parse.program forms)).Load.decls in
|
let incoming =
|
||||||
|
let ds = (Load.program ~file:t.file (Parse.program forms)).Load.decls in
|
||||||
|
match package_of t origin with
|
||||||
|
| None -> ds
|
||||||
|
| Some p ->
|
||||||
|
(* Qualified exactly as the import qualified them, so a redefined
|
||||||
|
[settle] lands on [sim/settle] and its call to [move-grain] lands on
|
||||||
|
[sim/move-grain]. A name the package does not own — the prelude's, or
|
||||||
|
another package's — is left alone, which is the same rule [Load] uses
|
||||||
|
at import time and the reason both go through [qualify_decl]. *)
|
||||||
|
let owns =
|
||||||
|
p.Load.owns
|
||||||
|
@ List.filter_map Ast.declared_name ds
|
||||||
|
in
|
||||||
|
List.map (Load.qualify_decl owns p.Load.alias) ds
|
||||||
|
in
|
||||||
let loc =
|
let loc =
|
||||||
match incoming with d :: _ -> d.Ast.dloc | [] -> Loc.unknown
|
match incoming with d :: _ -> d.Ast.dloc | [] -> Loc.unknown
|
||||||
in
|
in
|
||||||
|
|||||||
@ -127,6 +127,31 @@ let () =
|
|||||||
| exception Loc.Error (_, m) ->
|
| exception Loc.Error (_, m) ->
|
||||||
fail "reloading a file with imports failed: %s" m);
|
fail "reloading a file with imports failed: %s" m);
|
||||||
|
|
||||||
|
(* A form typed into a file that is *imported as a package* has to be
|
||||||
|
qualified the way the import qualified it, or it splices as a brand-new
|
||||||
|
unrelated name: the evaluation reports success and the running program
|
||||||
|
goes on calling the one it already had. The alias is chosen by the
|
||||||
|
importer and written nowhere in the file, so the path is the only thing
|
||||||
|
that can decide it — which is why it is derived here and not sent by the
|
||||||
|
editor. *)
|
||||||
|
let t, _ = Session.create ~file:"../sand.flan" in
|
||||||
|
(match
|
||||||
|
Session.eval ~origin:"../sand-sim/sim.flan" t
|
||||||
|
"(defn settle [row i32 col i32] Unit (do))"
|
||||||
|
with
|
||||||
|
| c ->
|
||||||
|
if c.Session.fns <> [ "sim/settle" ] then
|
||||||
|
fail "a form from a package file reported %s, wanted sim/settle"
|
||||||
|
(String.concat " " c.Session.fns)
|
||||||
|
| exception Loc.Error (_, m) -> fail "redefining sim/settle: %s" m);
|
||||||
|
(* And a file that is not a package keeps its names as written. *)
|
||||||
|
(match Session.eval ~origin:"../sand.flan" t "(defn game-draw [] Unit (do))" with
|
||||||
|
| c ->
|
||||||
|
if c.Session.fns <> [ "game-draw" ] then
|
||||||
|
fail "a form from the program's own file reported %s"
|
||||||
|
(String.concat " " c.Session.fns)
|
||||||
|
| exception Loc.Error (_, m) -> fail "redefining game-draw: %s" m);
|
||||||
|
|
||||||
if !failures = 0 then print_endline "session: all tests passed"
|
if !failures = 0 then print_endline "session: all tests passed"
|
||||||
else begin
|
else begin
|
||||||
Printf.printf "\n%d failure(s)\n" !failures;
|
Printf.printf "\n%d failure(s)\n" !failures;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user