A defn- is private to its module, the package's own macros may write calls to it, and the refusal names the fix
This commit is contained in:
parent
376e990689
commit
793b0eea9e
21
TODO.org
21
TODO.org
@ -524,13 +524,20 @@ both.
|
||||
|
||||
** DONE A package cannot mark a name private
|
||||
CLOSED: [2026-09-25]
|
||||
=defn-= declares a function private to its package; it is a =defn= otherwise.
|
||||
A use from a file outside the package's directory — a call or the name as a
|
||||
value, from the importer or from another package — is refused in =Check=, so it
|
||||
holds under C-c C-c too. Functions only. A single-file package's siblings share
|
||||
its directory and can call its =defn-=s, and a package macro expanding to a
|
||||
=defn-= call is refused at the expansion. The edn and json internals are now
|
||||
=defn-=; =rl/get-color-raw= no longer exists. =docs/BUILT.md= has the placement.
|
||||
=defn-= declares a function private to its module; it is a =defn= otherwise.
|
||||
The module is the import boundary: every file of a directory package, or the
|
||||
one file of a package imported by naming the file. A use from outside — a call
|
||||
or the name as a value — is refused in =Check=, so it holds under C-c C-c too.
|
||||
Code the package's own macro wrote counts as inside (SBCL's rule, not
|
||||
Clojure's); what the importer wrote, passed through or from its own macro, does
|
||||
not. Functions only. The edn and json internals are now =defn-=;
|
||||
=rl/get-color-raw= no longer exists. =docs/BUILT.md= has the placement.
|
||||
|
||||
** WAIT A private scoped to one file of a directory package
|
||||
Deferred until a need appears: Odin's =@(private="file")=, a function visible
|
||||
to its own file only when the package is a directory. =defn-= covers the
|
||||
package; a one-file package already gets file scope because the file is the
|
||||
module.
|
||||
|
||||
** CANCELLED A struct version word, so a redefined layout keeps working
|
||||
CLOSED: [2026-09-20]
|
||||
|
||||
@ -794,18 +794,29 @@ takes a macro-stamped location — and `Loc.from_macro` sets a name and leaves f
|
||||
the frame the break loop reports is still the line the reader is looking at. `temps` is not reset on this path, unlike `decl`'s — a declaration is
|
||||
a fresh top level, an expression is evaluated into a session that has been handing out temporaries all along.
|
||||
|
||||
A function can be private to its package: `defn-`, Clojure's spelling, is a `defn` whose uses outside the package's
|
||||
directory are refused. It is not done in `load.ml`, although `main`'s refusal is. `Load.refuse_hidden` walks every
|
||||
A function can be private to its module: `defn-`, Clojure's spelling, is a `defn` whose uses outside the module that
|
||||
declares it are refused. It is not done in `load.ml`, although `main`'s refusal is. `Load.refuse_hidden` walks every
|
||||
declaration after the rename, the package's own included, and by then the package's calls to its helper read
|
||||
`alias/helper` like anyone else's; and a C-c C-c goes through `Load.program` with one form and no imports in sight. So
|
||||
the flag rides on `Ast.fn`, `Check` records every `defn-` in `privates`, and `Check.private_ref` compares the directory
|
||||
of the file the use is written in with the directory of the file the definition is — the same shape as
|
||||
`shadows_builtin`'s file test. Being in `Check` is what makes it hold in the session too: every evaluation re-checks
|
||||
the whole declaration list. Only a qualified name is asked about, because an unqualified one is the program's own.
|
||||
Two consequences follow from the rule being about files. A single-file package shares its directory with its
|
||||
siblings, and they can call its `defn-`s. And a package macro that expands to a call to one of its `defn-`s is
|
||||
refused at the expansion, because the expansion is located at the call site in the importer's file; the edn and json
|
||||
readers' generated code calls `need-int` and its neighbours, which is why those stay `defn`.
|
||||
the flag rides on `Ast.fn`, `Check` records every `defn-` in `privates`, and `Check.private_ref` compares the file the
|
||||
use is written in with the file the definition is — the same shape as `shadows_builtin`'s file test. Being in `Check`
|
||||
is what makes it hold in the session too: every evaluation re-checks the whole declaration list. Only a qualified name
|
||||
is asked about, because an unqualified one is the program's own.
|
||||
|
||||
The module is what `Load` imports as one unit. For a directory package that is every `.flan` file in the directory, so
|
||||
the test is the two files' directories. For a package imported by naming a single file it is that file, and its
|
||||
siblings in the directory are not part of it; the parser cannot tell which kind it is reading, so `Load.file_scoped`
|
||||
narrows the flag to `Private_to_file` at import, and `Session.eval` does the same for a C-c C-c in such a file. A
|
||||
file-only private inside a directory package, Odin's `@(private="file")`, is not provided.
|
||||
|
||||
Code a package's own macro wrote counts as inside the package wherever it is expanded, which is SBCL's rule: an
|
||||
expansion there refers to the package's internal symbols freely. Clojure refuses the same thing. `Expand.unmarshal`
|
||||
already separates the two kinds of node an expansion holds. A node the macro invented carries the call site's
|
||||
location, tagged with the macro's name and no separate call site; a form the author wrote and the macro passed
|
||||
through keeps its own position, with the call recorded in `msite`. So an invented node whose macro has the function's
|
||||
qualifier is accepted, and an author's form is judged by the file it sits in. The macro name is the outermost one,
|
||||
because the tag is outermost-wins: a call written by a package macro that was itself expanded out of an importer's
|
||||
macro is judged as the importer's.
|
||||
|
||||
## The link follows the program
|
||||
|
||||
|
||||
10
lib/ast.ml
10
lib/ast.ml
@ -208,6 +208,12 @@ and pattern =
|
||||
makes each instantiation check the concrete type answers yes. *)
|
||||
type pred = { pname : string; pvar : string; ploc : Loc.t }
|
||||
|
||||
(* Who may use a function. [defn-] parses as [Private_to_package]; [Load]
|
||||
narrows it to [Private_to_file] when the package is a single file named
|
||||
outright, because that file is the whole module and its siblings in the
|
||||
directory are not part of it. [Check.private_ref] is what enforces both. *)
|
||||
type privacy = Exported | Private_to_package | Private_to_file
|
||||
|
||||
type fn = {
|
||||
name : string;
|
||||
params : field list;
|
||||
@ -225,9 +231,7 @@ type fn = {
|
||||
fwhere : pred list;
|
||||
fbody : expr list;
|
||||
nloc : Loc.t;
|
||||
(* Written [defn-]: callable only from the files of the package that
|
||||
declares it. [Check.private_ref] is the whole of what it means. *)
|
||||
fprivate : bool;
|
||||
fprivate : privacy;
|
||||
}
|
||||
|
||||
type decl = { d : decl_kind; dloc : Loc.t }
|
||||
|
||||
77
lib/check.ml
77
lib/check.ml
@ -114,8 +114,9 @@ type env = {
|
||||
function can show it. Kept apart from [fparams] because a foreign
|
||||
[declare] has a location and no parameter vector worth showing. *)
|
||||
fn_locs : (string, Loc.t) Hashtbl.t;
|
||||
(* Every [defn-], by name, with where it was written. See [private_ref]. *)
|
||||
privates : (string, Loc.t) Hashtbl.t;
|
||||
(* Every [defn-], by name, with where it was written and how far it is
|
||||
visible. See [private_ref]. *)
|
||||
privates : (string, Loc.t * Ast.privacy) Hashtbl.t;
|
||||
globals : (string, Types.t * bool) Hashtbl.t; (* type, is a constant *)
|
||||
(* Where each global was declared, so a refusal about one can show it. A
|
||||
second table rather than a third field, because every other reader of
|
||||
@ -9008,32 +9009,61 @@ and ordinary_call ctx ~want loc name args =
|
||||
is not the file the defn was written in, so it reaches the builtin. That
|
||||
is the conservative direction, and C-c C-c — which sends the buffer's own
|
||||
path — is not affected. *)
|
||||
(* A [defn-] is its package's own. A use of one — a call, or the name taken
|
||||
as a value — is refused unless it is written in a file of the package that
|
||||
declares it, and a package is its directory, so the test is the directory of
|
||||
the two files. Realpath'd, because one side is usually the path the importer
|
||||
was given on the command line and the other the one [Load] resolved.
|
||||
(* A [defn-] is its module's own. A use of one — a call, or the name taken
|
||||
as a value — is refused unless it is written inside the module that
|
||||
declares it. A directory package is every file in its directory, so the
|
||||
test there is the two files' directories; a single file imported outright
|
||||
is that file alone, which [Load] records by narrowing the flag to
|
||||
[Private_to_file]. Realpath'd, because one side is usually the path the
|
||||
importer was given on the command line and the other the one [Load]
|
||||
resolved.
|
||||
|
||||
Code the package's own macro wrote counts as inside, wherever it was
|
||||
expanded: SBCL's rule, where a macro's expansion refers to its package's
|
||||
internal symbols freely. What the importer wrote itself does not, even when
|
||||
it is an argument the macro passed through. [Expand.unmarshal] tells the two
|
||||
apart already: a node the macro invented carries the call site's location
|
||||
with the macro's name on it and no separate call site, and a form the author
|
||||
wrote keeps its own position with the call recorded beside it. The macro's
|
||||
package is its qualifier, which is the function's when both come from the
|
||||
same module, because an import qualifies every name a directory declares
|
||||
under the one alias it is read as.
|
||||
|
||||
Only a qualified name is asked about. An unqualified one belongs to the
|
||||
program being built, and nothing outside a program can name it — the REPL,
|
||||
which evaluates with no file behind it, included.
|
||||
|
||||
A package that is a single file named outright, rather than a directory,
|
||||
shares its directory with whatever sits beside it, so a sibling file that
|
||||
imports it can call its [defn-]s. *)
|
||||
program being built, and nothing outside a program can name it. *)
|
||||
and private_ref ctx loc name =
|
||||
let qualifier n =
|
||||
match String.rindex_opt n '/' with
|
||||
| Some i -> Some (String.sub n 0 i)
|
||||
| None -> None
|
||||
in
|
||||
let written_by_own_macro () =
|
||||
match loc.Loc.macro, loc.Loc.msite with
|
||||
| Some m, None -> qualifier m <> None && qualifier m = qualifier name
|
||||
| _ -> false
|
||||
in
|
||||
match Hashtbl.find_opt ctx.env.privates name with
|
||||
| Some at when String.contains name '/' ->
|
||||
let dir file =
|
||||
Filename.dirname
|
||||
(try Unix.realpath file with Unix.Unix_error _ -> file)
|
||||
| Some (at, scope) when String.contains name '/' ->
|
||||
let real file = try Unix.realpath file with Unix.Unix_error _ -> file in
|
||||
let inside =
|
||||
match scope with
|
||||
| Ast.Private_to_file -> String.equal (real at.Loc.file) (real loc.Loc.file)
|
||||
| _ ->
|
||||
String.equal (Filename.dirname (real at.Loc.file))
|
||||
(Filename.dirname (real loc.Loc.file))
|
||||
in
|
||||
if not (String.equal (dir at.Loc.file) (dir loc.Loc.file)) then
|
||||
if not inside && not (written_by_own_macro ()) then begin
|
||||
let where =
|
||||
match scope with
|
||||
| Ast.Private_to_file -> real at.Loc.file
|
||||
| _ -> "the files in " ^ Filename.dirname (real at.Loc.file)
|
||||
in
|
||||
Loc.failk "check/private" loc
|
||||
~notes:[ Loc.note at (Printf.sprintf "%s is declared here" name) ]
|
||||
"%s is private to its package: it is declared with defn-, and a \
|
||||
defn- can be used only from a file in %s"
|
||||
name (dir at.Loc.file)
|
||||
"%s is private to its package: it is declared with defn-, so only %s \
|
||||
can use it. Declaring it with defn instead makes it usable from here"
|
||||
name where
|
||||
end
|
||||
| _ -> ()
|
||||
|
||||
and shadows_builtin ctx loc name =
|
||||
@ -10472,8 +10502,9 @@ let collect env (decls : Ast.decl list) =
|
||||
in
|
||||
env.tyvars <- [];
|
||||
env.tvpreds <- [];
|
||||
if fn.Ast.fprivate then
|
||||
Hashtbl.replace env.privates fn.Ast.name fn.Ast.nloc;
|
||||
if fn.Ast.fprivate <> Ast.Exported then
|
||||
Hashtbl.replace env.privates fn.Ast.name
|
||||
(fn.Ast.nloc, fn.Ast.fprivate);
|
||||
if vars = [] then begin
|
||||
Hashtbl.replace env.fns fn.Ast.name (params, ret);
|
||||
Hashtbl.replace env.fparams fn.Ast.name fn.Ast.params;
|
||||
|
||||
@ -838,7 +838,7 @@ let of_dump ~env ~taken ~bound_syms ~config (d : dump) : imported =
|
||||
decls :=
|
||||
{ Ast.d =
|
||||
Ast.DeclareC
|
||||
({ Ast.name = flan; params; praw = None; ret; fwhere = []; fbody = []; nloc = f.cloc; fprivate = false },
|
||||
({ Ast.name = flan; params; praw = None; ret; fwhere = []; fbody = []; nloc = f.cloc; fprivate = Ast.Exported },
|
||||
f.csym);
|
||||
dloc = f.cloc }
|
||||
:: !decls)
|
||||
|
||||
@ -175,7 +175,7 @@ let constructor n slots loc : Ast.decl =
|
||||
Ast.Defn
|
||||
{ Ast.name = n; params; praw = None; ret = Some (dyn_at loc);
|
||||
fwhere = []; fbody = [ ex loc (Ast.MapLit (Some n, pairs)) ];
|
||||
nloc = loc; fprivate = false };
|
||||
nloc = loc; fprivate = Ast.Exported };
|
||||
dloc = loc }
|
||||
|
||||
(* The dispatch value a method answers for, as an expression to compare
|
||||
|
||||
17
lib/load.ml
17
lib/load.ml
@ -26,7 +26,7 @@
|
||||
would collide with the importer's, and worse, would keep everything it
|
||||
calls reachable (see [Reach]) — which for a raylib front-end is the whole
|
||||
library, on the target that cannot link it. And a [defn-] is exported like
|
||||
any other name but refused at a use outside its package's directory, which
|
||||
any other name but refused at a use outside its package, which
|
||||
is [Check.private_ref]'s and not this module's: the rename has to qualify
|
||||
the package's own calls to it exactly as it qualifies everything else.
|
||||
|
||||
@ -903,6 +903,20 @@ let refuse_hidden hidden ds =
|
||||
|
||||
(* Every top-level name the package declares — types and values alike, since a
|
||||
use site is rewritten by name and the two never collide in one namespace. *)
|
||||
(* A [defn-] in a package that is one file named outright is private to that
|
||||
file: the file is the whole module, and whatever else sits in its directory
|
||||
is not part of it. The parser cannot know which kind of package it is
|
||||
reading, so the flag is narrowed here and in [Session.eval], the two places
|
||||
that do. *)
|
||||
let file_scoped (ds : Ast.decl list) =
|
||||
List.map
|
||||
(fun (d : Ast.decl) ->
|
||||
match d.Ast.d with
|
||||
| Ast.Defn ({ Ast.fprivate = Ast.Private_to_package; _ } as fn) ->
|
||||
{ d with Ast.d = Ast.Defn { fn with Ast.fprivate = Ast.Private_to_file } }
|
||||
| _ -> d)
|
||||
ds
|
||||
|
||||
let owned_names (ds : Ast.decl list) =
|
||||
List.filter_map Ast.declared_name ds
|
||||
|
||||
@ -1413,6 +1427,7 @@ let rec import ~seen ~open_ ~loc alias dir =
|
||||
in
|
||||
let owned = List.filter exported (owned_names ds) in
|
||||
let decls = List.map (qualify_decl owned alias) own in
|
||||
let decls = if one_file then file_scoped decls else decls in
|
||||
let lflags = if one_file then [] else link_flags dir in
|
||||
let csrcs = if one_file then [] else entries dir ".c" in
|
||||
let phidden =
|
||||
|
||||
14
lib/parse.ml
14
lib/parse.ml
@ -1377,7 +1377,9 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
(* [defn-] is a [defn] in every respect but one: [Check.private_ref] refuses
|
||||
a use of it from outside the package that declares it. *)
|
||||
| List ({ v = Sym ("defn" | "defn-" as head); _ } :: args) ->
|
||||
let fprivate = String.equal head "defn-" in
|
||||
let fprivate =
|
||||
if String.equal head "defn-" then Ast.Private_to_package else Ast.Exported
|
||||
in
|
||||
(match args with
|
||||
| n :: { v = Vec ps; _ } :: ret :: body ->
|
||||
(* The slot's own failure, because the thing found there is almost
|
||||
@ -1467,7 +1469,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
else fun fn -> Ast.Defmulti fn)
|
||||
{ Ast.name = sym n; params = dyn_params which ps; praw = None;
|
||||
ret = Some (texpr ret); fwhere = []; fbody = body_of body;
|
||||
nloc = n.loc; fprivate = false })
|
||||
nloc = n.loc; fprivate = Ast.Exported })
|
||||
| _ -> fail f "%s" usage)
|
||||
|
||||
| List ({ v = Sym "defmethod"; _ } :: args) ->
|
||||
@ -1481,7 +1483,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
mfn = { Ast.name = gen ^ "@" ^ Ast.dispatch_text k;
|
||||
params = dyn_params "defmethod" ps; praw = None;
|
||||
ret = None; fwhere = []; fbody = body_of body;
|
||||
nloc = n.loc; fprivate = false } })
|
||||
nloc = n.loc; fprivate = Ast.Exported } })
|
||||
| _ ->
|
||||
fail f
|
||||
"defmethod is (defmethod generic dispatch [param ...] body ...). \
|
||||
@ -1513,11 +1515,11 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
| [ n; { v = Form.Vec ps; _ } ] ->
|
||||
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
|
||||
ret = None; fwhere = []; fbody = []; nloc = n.loc;
|
||||
fprivate = false } csym)
|
||||
fprivate = Ast.Exported } csym)
|
||||
| [ n; { v = Form.Vec ps; _ }; r ] ->
|
||||
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
|
||||
ret = Some (texpr r); fwhere = []; fbody = [];
|
||||
nloc = n.loc; fprivate = false } csym)
|
||||
nloc = n.loc; fprivate = Ast.Exported } csym)
|
||||
| _ -> fail f "%s" usage)
|
||||
| _ -> fail f "%s" usage)
|
||||
|
||||
@ -1749,7 +1751,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
leave off. *)
|
||||
praw = None;
|
||||
ret = Some form_t; fwhere = []; fbody = macro_body sg body;
|
||||
nloc = n.loc; fprivate = false })
|
||||
nloc = n.loc; fprivate = Ast.Exported })
|
||||
| _ ->
|
||||
fail f "defmacro is (defmacro name [param ...] body ...)")
|
||||
|
||||
|
||||
@ -644,7 +644,8 @@ let eval ?(origin = "<eval>") ?pause t src : change =
|
||||
p.Load.owns
|
||||
@ List.filter_map Ast.declared_name ds
|
||||
in
|
||||
List.map (Load.qualify_decl owns p.Load.alias) ds
|
||||
let ds = List.map (Load.qualify_decl owns p.Load.alias) ds in
|
||||
if Load.is_package_file p.Load.dir then Load.file_scoped ds else ds
|
||||
in
|
||||
let loc =
|
||||
match incoming with d :: _ -> d.Ast.dloc | [] -> Loc.unknown
|
||||
|
||||
10
test/dune
10
test/dune
@ -68,6 +68,7 @@
|
||||
; pkg-private*.flan.
|
||||
(glob_files programs/pkgs/secret/*)
|
||||
(glob_files programs/pkgs/nosy/*)
|
||||
(glob_files programs/loose/*)
|
||||
; The synthetic C header the importer's table reads. Committed rather than
|
||||
; reached for on the machine: the raylib case needs raylib installed, at the
|
||||
; right version, with a variable set, so it skips everywhere and covers
|
||||
@ -241,7 +242,8 @@
|
||||
(glob_files programs/pkgs/shadowed/*)
|
||||
(glob_files programs/pkgs/gen/*)
|
||||
(glob_files programs/pkgs/secret/*)
|
||||
(glob_files programs/pkgs/nosy/*))
|
||||
(glob_files programs/pkgs/nosy/*)
|
||||
(glob_files programs/loose/*))
|
||||
(action (run ./test_valgrind.exe)))
|
||||
|
||||
; The corpus a fourth time, through the hand-written x86-64 backend, compared
|
||||
@ -303,7 +305,8 @@
|
||||
(glob_files programs/pkgs/shadowed/*)
|
||||
(glob_files programs/pkgs/gen/*)
|
||||
(glob_files programs/pkgs/secret/*)
|
||||
(glob_files programs/pkgs/nosy/*))
|
||||
(glob_files programs/pkgs/nosy/*)
|
||||
(glob_files programs/loose/*))
|
||||
(action
|
||||
(setenv SURVEY_STRICT 1
|
||||
(setenv SURVEY_QUIET 1
|
||||
@ -482,7 +485,8 @@
|
||||
(glob_files programs/pkgs/shadowed/*)
|
||||
(glob_files programs/pkgs/gen/*)
|
||||
(glob_files programs/pkgs/secret/*)
|
||||
(glob_files programs/pkgs/nosy/*))
|
||||
(glob_files programs/pkgs/nosy/*)
|
||||
(glob_files programs/loose/*))
|
||||
(action
|
||||
(setenv SURVEY_STRICT 1
|
||||
(setenv SURVEY_QUIET 1
|
||||
|
||||
7
test/programs/loose/lib.flan
Normal file
7
test/programs/loose/lib.flan
Normal file
@ -0,0 +1,7 @@
|
||||
;;;; A package that is one file, imported by naming the file. Its defn- is
|
||||
;;;; private to this file: the files beside it in the directory are not part
|
||||
;;;; of it.
|
||||
|
||||
(defn- inner [a i32] i32 (* a 3))
|
||||
|
||||
(defn outer [a i32] i32 (inner a))
|
||||
8
test/programs/loose/peek.flan
Normal file
8
test/programs/loose/peek.flan
Normal file
@ -0,0 +1,8 @@
|
||||
;;;; Sits beside lib.flan and imports it by name. lib's defn- is private to
|
||||
;;;; lib.flan, so the shared directory does not make it callable here.
|
||||
|
||||
(import lib "lib.flan")
|
||||
|
||||
(defn main [] i32
|
||||
(print (lib/inner 5))
|
||||
0)
|
||||
7
test/programs/loose/use.flan
Normal file
7
test/programs/loose/use.flan
Normal file
@ -0,0 +1,7 @@
|
||||
;;;; Imports lib.flan by name and calls its public function.
|
||||
|
||||
(import lib "lib.flan")
|
||||
|
||||
(defn main [] i32
|
||||
(print (lib/outer 5)) (println "")
|
||||
0)
|
||||
10
test/programs/pkg-private-own-macro.flan
Normal file
10
test/programs/pkg-private-own-macro.flan
Normal file
@ -0,0 +1,10 @@
|
||||
;;;; A call to a package's private function written by a macro of this file,
|
||||
;;;; not of the package.
|
||||
|
||||
(import secret "pkgs/secret")
|
||||
|
||||
(defmacro sneak [& args] `(do (secret/mix 1 2)))
|
||||
|
||||
(defn main [] i32
|
||||
(print (sneak))
|
||||
0)
|
||||
9
test/programs/pkg-private-passed.flan
Normal file
9
test/programs/pkg-private-passed.flan
Normal file
@ -0,0 +1,9 @@
|
||||
;;;; A call to a package's private function written here and handed to the
|
||||
;;;; package's own macro. The macro passes it through; the call is still this
|
||||
;;;; file's.
|
||||
|
||||
(import secret "pkgs/secret")
|
||||
|
||||
(defn main [] i32
|
||||
(print (secret/pass (secret/mix 1 2)))
|
||||
0)
|
||||
@ -1,5 +1,6 @@
|
||||
;;;; A package's private function, used from inside the package: from the
|
||||
;;;; file that declares it, from the package's other file, and as a value.
|
||||
;;;; file that declares it, from the package's other file, as a value, and
|
||||
;;;; through code the package's own macros write here.
|
||||
|
||||
(import secret "pkgs/secret")
|
||||
|
||||
@ -7,4 +8,6 @@
|
||||
(print (secret/combine 1 2)) (println "")
|
||||
(print (secret/twice 3)) (println "")
|
||||
(print (secret/via-value 4 5)) (println "")
|
||||
(print (secret/mixed 6 7)) (println "")
|
||||
(print (secret/mixed-in-do 8 9)) (println "")
|
||||
0)
|
||||
|
||||
@ -5,3 +5,14 @@
|
||||
(defn- mix [a i32 b i32] i32 (+ (* a 10) b))
|
||||
|
||||
(defn combine [a i32 b i32] i32 (mix a b))
|
||||
|
||||
;; Code these macros write is the package's own, wherever it is expanded: the
|
||||
;; call to mix they answer with is accepted in the importer's file, on its own
|
||||
;; and nested inside a do.
|
||||
(defmacro mixed [& args] `(mix ~(at args 0) ~(at args 1)))
|
||||
|
||||
(defmacro mixed-in-do [& args] `(do (mix ~(at args 0) ~(at args 1))))
|
||||
|
||||
;; What the importer writes and this macro only passes through is still the
|
||||
;; importer's.
|
||||
(defmacro pass [& args] (at args 0))
|
||||
|
||||
@ -2796,7 +2796,12 @@ let () =
|
||||
declares it, the package's other file, and as a value handed out from
|
||||
there. The refusals are with the others, further down. *)
|
||||
outputs "a defn- used inside its package" "programs/pkg-private.flan"
|
||||
"12\n33\n45\n";
|
||||
"12\n33\n45\n67\n89\n";
|
||||
(* A single-file package's defn- is callable from that file, and the file
|
||||
beside it that imports it reaches the public function. The refusal for
|
||||
the same sibling calling the defn- directly is with the others. *)
|
||||
outputs "a single-file package calls its own defn-"
|
||||
"programs/loose/use.flan" "15\n";
|
||||
(* A defn named after a builtin, and the boundary the shadow stops at.
|
||||
The numbers are the whole claim and none of them could be printed by
|
||||
the other reading: 7 is the program's own one-argument (get p), which
|
||||
@ -3085,6 +3090,20 @@ let () =
|
||||
"programs/pkg-private-nested.flan" "secret/mix is private to its package";
|
||||
refuses "and the refusal names the package's directory"
|
||||
"programs/pkg-private-call.flan" "pkgs/secret";
|
||||
refuses "and names the fix"
|
||||
"programs/pkg-private-call.flan" "Declaring it with defn instead";
|
||||
(* The package's own macro may write a call to its defn- (that half is in
|
||||
pkg-private.flan); what the importer writes is the importer's, whether
|
||||
the package's macro passes it through or the importer's macro wrote it. *)
|
||||
refuses "a defn- call the importer wrote, passed through the package's macro"
|
||||
"programs/pkg-private-passed.flan" "secret/mix is private to its package";
|
||||
refuses "a defn- call written by the importer's own macro"
|
||||
"programs/pkg-private-own-macro.flan"
|
||||
"secret/mix is private to its package";
|
||||
(* A single file named outright is the whole module, so its defn- is not
|
||||
visible to the file beside it in the directory. *)
|
||||
refuses "a single-file package's defn-, from the file beside it"
|
||||
"programs/loose/peek.flan" "lib/inner is private to its package";
|
||||
refuses "one directory under two aliases" "programs/pkg-two-aliases.flan"
|
||||
"one directory takes one alias";
|
||||
(* A ring is refused and the ring is named. The needle is the chain, not
|
||||
|
||||
@ -615,6 +615,20 @@ let () =
|
||||
| _ -> ()
|
||||
| exception Loc.Error { Loc.dmsg = m; _ } ->
|
||||
fail "a defn- in the program's own file: %s" m);
|
||||
(* And a single-file package: redefined from its own file, it stays private
|
||||
to that file, so the file beside it that imports it is still refused. *)
|
||||
let tl, _ = Session.create ~file:"programs/loose/use.flan" () in
|
||||
(match Session.eval ~origin:"programs/loose/lib.flan" tl
|
||||
"(defn- inner [a i32] i32 (* a 4))" with
|
||||
| _ -> ()
|
||||
| exception Loc.Error { Loc.dmsg = m; _ } ->
|
||||
fail "redefining a single-file package's defn-: %s" m);
|
||||
(match Session.eval ~origin:"programs/loose/use.flan" tl
|
||||
"(defn peek [] i32 (lib/inner 1))" with
|
||||
| _ -> fail "a redefined single-file defn- became callable beside its file"
|
||||
| exception Loc.Error { Loc.dmsg = m; _ } ->
|
||||
if not (has m "lib/inner is private to its package") then
|
||||
fail "a call to a single-file defn- was refused for another reason: %s" m);
|
||||
|
||||
(* ── C-x C-e expands, which it never used to ────────────────────────
|
||||
[Parse.expr] did not call the expander at all, so an expression typed at
|
||||
|
||||
@ -1528,11 +1528,12 @@ existed.</p>
|
||||
|
||||
<p><strong>A function declared with <code>defn-</code> is private to its package.</strong>
|
||||
It is a <code>defn</code> in every other respect, and every file in the package's
|
||||
directory can call it. A use from anywhere else — a call, or the name passed as a
|
||||
value — is refused at compile time, and the message names the function and the
|
||||
directory it belongs to. Only functions have a private form. A single-file package
|
||||
shares its directory with the files beside it, so those files can call its
|
||||
<code>defn-</code>s too.</p>
|
||||
directory can call it. For a package imported by naming a single file, the package is
|
||||
that file alone, and the other files in its directory cannot call it. A use from
|
||||
anywhere else — a call, or the name passed as a value — is refused at compile time,
|
||||
and the message names the function and where it may be used. Code written by one of
|
||||
the package's own macros counts as the package's, wherever the macro is called. Only
|
||||
functions have a private form.</p>
|
||||
|
||||
<p>A package may carry the C it binds to. Every <code>.c</code> file in the directory is
|
||||
compiled into the build, and a file named <code>link</code> lists extra linker
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user