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
|
** DONE A package cannot mark a name private
|
||||||
CLOSED: [2026-09-25]
|
CLOSED: [2026-09-25]
|
||||||
=defn-= declares a function private to its package; it is a =defn= otherwise.
|
=defn-= declares a function private to its module; it is a =defn= otherwise.
|
||||||
A use from a file outside the package's directory — a call or the name as a
|
The module is the import boundary: every file of a directory package, or the
|
||||||
value, from the importer or from another package — is refused in =Check=, so it
|
one file of a package imported by naming the file. A use from outside — a call
|
||||||
holds under C-c C-c too. Functions only. A single-file package's siblings share
|
or the name as a value — is refused in =Check=, so it holds under C-c C-c too.
|
||||||
its directory and can call its =defn-=s, and a package macro expanding to a
|
Code the package's own macro wrote counts as inside (SBCL's rule, not
|
||||||
=defn-= call is refused at the expansion. The edn and json internals are now
|
Clojure's); what the importer wrote, passed through or from its own macro, does
|
||||||
=defn-=; =rl/get-color-raw= no longer exists. =docs/BUILT.md= has the placement.
|
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
|
** CANCELLED A struct version word, so a redefined layout keeps working
|
||||||
CLOSED: [2026-09-20]
|
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
|
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 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
|
A function can be private to its module: `defn-`, Clojure's spelling, is a `defn` whose uses outside the module that
|
||||||
directory are refused. It is not done in `load.ml`, although `main`'s refusal is. `Load.refuse_hidden` walks every
|
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
|
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
|
`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
|
the flag rides on `Ast.fn`, `Check` records every `defn-` in `privates`, and `Check.private_ref` compares the file the
|
||||||
of the file the use is written in with the directory of the file the definition is — the same shape as
|
use is written in with the file the definition is — the same shape as `shadows_builtin`'s file test. Being in `Check`
|
||||||
`shadows_builtin`'s file test. Being in `Check` is what makes it hold in the session too: every evaluation re-checks
|
is what makes it hold in the session too: every evaluation re-checks the whole declaration list. Only a qualified name
|
||||||
the whole declaration list. Only a qualified name is asked about, because an unqualified one is the program's own.
|
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
|
The module is what `Load` imports as one unit. For a directory package that is every `.flan` file in the directory, so
|
||||||
refused at the expansion, because the expansion is located at the call site in the importer's file; the edn and json
|
the test is the two files' directories. For a package imported by naming a single file it is that file, and its
|
||||||
readers' generated code calls `need-int` and its neighbours, which is why those stay `defn`.
|
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
|
## 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. *)
|
makes each instantiation check the concrete type answers yes. *)
|
||||||
type pred = { pname : string; pvar : string; ploc : Loc.t }
|
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 = {
|
type fn = {
|
||||||
name : string;
|
name : string;
|
||||||
params : field list;
|
params : field list;
|
||||||
@ -225,9 +231,7 @@ type fn = {
|
|||||||
fwhere : pred list;
|
fwhere : pred list;
|
||||||
fbody : expr list;
|
fbody : expr list;
|
||||||
nloc : Loc.t;
|
nloc : Loc.t;
|
||||||
(* Written [defn-]: callable only from the files of the package that
|
fprivate : privacy;
|
||||||
declares it. [Check.private_ref] is the whole of what it means. *)
|
|
||||||
fprivate : bool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type decl = { d : decl_kind; dloc : Loc.t }
|
type decl = { d : decl_kind; dloc : Loc.t }
|
||||||
|
|||||||
79
lib/check.ml
79
lib/check.ml
@ -114,8 +114,9 @@ type env = {
|
|||||||
function can show it. Kept apart from [fparams] because a foreign
|
function can show it. Kept apart from [fparams] because a foreign
|
||||||
[declare] has a location and no parameter vector worth showing. *)
|
[declare] has a location and no parameter vector worth showing. *)
|
||||||
fn_locs : (string, Loc.t) Hashtbl.t;
|
fn_locs : (string, Loc.t) Hashtbl.t;
|
||||||
(* Every [defn-], by name, with where it was written. See [private_ref]. *)
|
(* Every [defn-], by name, with where it was written and how far it is
|
||||||
privates : (string, Loc.t) Hashtbl.t;
|
visible. See [private_ref]. *)
|
||||||
|
privates : (string, Loc.t * Ast.privacy) Hashtbl.t;
|
||||||
globals : (string, Types.t * bool) Hashtbl.t; (* type, is a constant *)
|
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
|
(* 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
|
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 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
|
is the conservative direction, and C-c C-c — which sends the buffer's own
|
||||||
path — is not affected. *)
|
path — is not affected. *)
|
||||||
(* A [defn-] is its package's own. A use of one — a call, or the name taken
|
(* 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 in a file of the package that
|
as a value — is refused unless it is written inside the module that
|
||||||
declares it, and a package is its directory, so the test is the directory of
|
declares it. A directory package is every file in its directory, so the
|
||||||
the two files. Realpath'd, because one side is usually the path the importer
|
test there is the two files' directories; a single file imported outright
|
||||||
was given on the command line and the other the one [Load] resolved.
|
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
|
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,
|
program being built, and nothing outside a program can name it. *)
|
||||||
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. *)
|
|
||||||
and private_ref ctx loc name =
|
and private_ref ctx loc name =
|
||||||
match Hashtbl.find_opt ctx.env.privates name with
|
let qualifier n =
|
||||||
| Some at when String.contains name '/' ->
|
match String.rindex_opt n '/' with
|
||||||
let dir file =
|
| Some i -> Some (String.sub n 0 i)
|
||||||
Filename.dirname
|
| None -> None
|
||||||
(try Unix.realpath file with Unix.Unix_error _ -> file)
|
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, 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 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
|
in
|
||||||
if not (String.equal (dir at.Loc.file) (dir loc.Loc.file)) then
|
|
||||||
Loc.failk "check/private" loc
|
Loc.failk "check/private" loc
|
||||||
~notes:[ Loc.note at (Printf.sprintf "%s is declared here" name) ]
|
~notes:[ Loc.note at (Printf.sprintf "%s is declared here" name) ]
|
||||||
"%s is private to its package: it is declared with defn-, and a \
|
"%s is private to its package: it is declared with defn-, so only %s \
|
||||||
defn- can be used only from a file in %s"
|
can use it. Declaring it with defn instead makes it usable from here"
|
||||||
name (dir at.Loc.file)
|
name where
|
||||||
|
end
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
|
|
||||||
and shadows_builtin ctx loc name =
|
and shadows_builtin ctx loc name =
|
||||||
@ -10472,8 +10502,9 @@ let collect env (decls : Ast.decl list) =
|
|||||||
in
|
in
|
||||||
env.tyvars <- [];
|
env.tyvars <- [];
|
||||||
env.tvpreds <- [];
|
env.tvpreds <- [];
|
||||||
if fn.Ast.fprivate then
|
if fn.Ast.fprivate <> Ast.Exported then
|
||||||
Hashtbl.replace env.privates fn.Ast.name fn.Ast.nloc;
|
Hashtbl.replace env.privates fn.Ast.name
|
||||||
|
(fn.Ast.nloc, fn.Ast.fprivate);
|
||||||
if vars = [] then begin
|
if vars = [] then begin
|
||||||
Hashtbl.replace env.fns fn.Ast.name (params, ret);
|
Hashtbl.replace env.fns fn.Ast.name (params, ret);
|
||||||
Hashtbl.replace env.fparams fn.Ast.name fn.Ast.params;
|
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 :=
|
decls :=
|
||||||
{ Ast.d =
|
{ Ast.d =
|
||||||
Ast.DeclareC
|
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);
|
f.csym);
|
||||||
dloc = f.cloc }
|
dloc = f.cloc }
|
||||||
:: !decls)
|
:: !decls)
|
||||||
|
|||||||
@ -175,7 +175,7 @@ let constructor n slots loc : Ast.decl =
|
|||||||
Ast.Defn
|
Ast.Defn
|
||||||
{ Ast.name = n; params; praw = None; ret = Some (dyn_at loc);
|
{ Ast.name = n; params; praw = None; ret = Some (dyn_at loc);
|
||||||
fwhere = []; fbody = [ ex loc (Ast.MapLit (Some n, pairs)) ];
|
fwhere = []; fbody = [ ex loc (Ast.MapLit (Some n, pairs)) ];
|
||||||
nloc = loc; fprivate = false };
|
nloc = loc; fprivate = Ast.Exported };
|
||||||
dloc = loc }
|
dloc = loc }
|
||||||
|
|
||||||
(* The dispatch value a method answers for, as an expression to compare
|
(* 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
|
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
|
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
|
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
|
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.
|
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
|
(* 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. *)
|
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) =
|
let owned_names (ds : Ast.decl list) =
|
||||||
List.filter_map Ast.declared_name ds
|
List.filter_map Ast.declared_name ds
|
||||||
|
|
||||||
@ -1413,6 +1427,7 @@ let rec import ~seen ~open_ ~loc alias dir =
|
|||||||
in
|
in
|
||||||
let owned = List.filter exported (owned_names ds) in
|
let owned = List.filter exported (owned_names ds) in
|
||||||
let decls = List.map (qualify_decl owned alias) own 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 lflags = if one_file then [] else link_flags dir in
|
||||||
let csrcs = if one_file then [] else entries dir ".c" in
|
let csrcs = if one_file then [] else entries dir ".c" in
|
||||||
let phidden =
|
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
|
(* [defn-] is a [defn] in every respect but one: [Check.private_ref] refuses
|
||||||
a use of it from outside the package that declares it. *)
|
a use of it from outside the package that declares it. *)
|
||||||
| List ({ v = Sym ("defn" | "defn-" as head); _ } :: args) ->
|
| 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
|
(match args with
|
||||||
| n :: { v = Vec ps; _ } :: ret :: body ->
|
| n :: { v = Vec ps; _ } :: ret :: body ->
|
||||||
(* The slot's own failure, because the thing found there is almost
|
(* 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)
|
else fun fn -> Ast.Defmulti fn)
|
||||||
{ Ast.name = sym n; params = dyn_params which ps; praw = None;
|
{ Ast.name = sym n; params = dyn_params which ps; praw = None;
|
||||||
ret = Some (texpr ret); fwhere = []; fbody = body_of body;
|
ret = Some (texpr ret); fwhere = []; fbody = body_of body;
|
||||||
nloc = n.loc; fprivate = false })
|
nloc = n.loc; fprivate = Ast.Exported })
|
||||||
| _ -> fail f "%s" usage)
|
| _ -> fail f "%s" usage)
|
||||||
|
|
||||||
| List ({ v = Sym "defmethod"; _ } :: args) ->
|
| 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;
|
mfn = { Ast.name = gen ^ "@" ^ Ast.dispatch_text k;
|
||||||
params = dyn_params "defmethod" ps; praw = None;
|
params = dyn_params "defmethod" ps; praw = None;
|
||||||
ret = None; fwhere = []; fbody = body_of body;
|
ret = None; fwhere = []; fbody = body_of body;
|
||||||
nloc = n.loc; fprivate = false } })
|
nloc = n.loc; fprivate = Ast.Exported } })
|
||||||
| _ ->
|
| _ ->
|
||||||
fail f
|
fail f
|
||||||
"defmethod is (defmethod generic dispatch [param ...] body ...). \
|
"defmethod is (defmethod generic dispatch [param ...] body ...). \
|
||||||
@ -1513,11 +1515,11 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
| [ n; { v = Form.Vec ps; _ } ] ->
|
| [ n; { v = Form.Vec ps; _ } ] ->
|
||||||
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
|
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
|
||||||
ret = None; fwhere = []; fbody = []; nloc = n.loc;
|
ret = None; fwhere = []; fbody = []; nloc = n.loc;
|
||||||
fprivate = false } csym)
|
fprivate = Ast.Exported } csym)
|
||||||
| [ n; { v = Form.Vec ps; _ }; r ] ->
|
| [ n; { v = Form.Vec ps; _ }; r ] ->
|
||||||
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
|
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
|
||||||
ret = Some (texpr r); fwhere = []; fbody = [];
|
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)
|
||||||
| _ -> fail f "%s" usage)
|
| _ -> fail f "%s" usage)
|
||||||
|
|
||||||
@ -1749,7 +1751,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
leave off. *)
|
leave off. *)
|
||||||
praw = None;
|
praw = None;
|
||||||
ret = Some form_t; fwhere = []; fbody = macro_body sg body;
|
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 ...)")
|
fail f "defmacro is (defmacro name [param ...] body ...)")
|
||||||
|
|
||||||
|
|||||||
@ -644,7 +644,8 @@ let eval ?(origin = "<eval>") ?pause t src : change =
|
|||||||
p.Load.owns
|
p.Load.owns
|
||||||
@ List.filter_map Ast.declared_name ds
|
@ List.filter_map Ast.declared_name ds
|
||||||
in
|
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
|
in
|
||||||
let loc =
|
let loc =
|
||||||
match incoming with d :: _ -> d.Ast.dloc | [] -> Loc.unknown
|
match incoming with d :: _ -> d.Ast.dloc | [] -> Loc.unknown
|
||||||
|
|||||||
10
test/dune
10
test/dune
@ -68,6 +68,7 @@
|
|||||||
; pkg-private*.flan.
|
; pkg-private*.flan.
|
||||||
(glob_files programs/pkgs/secret/*)
|
(glob_files programs/pkgs/secret/*)
|
||||||
(glob_files programs/pkgs/nosy/*)
|
(glob_files programs/pkgs/nosy/*)
|
||||||
|
(glob_files programs/loose/*)
|
||||||
; The synthetic C header the importer's table reads. Committed rather than
|
; 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
|
; 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
|
; 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/shadowed/*)
|
||||||
(glob_files programs/pkgs/gen/*)
|
(glob_files programs/pkgs/gen/*)
|
||||||
(glob_files programs/pkgs/secret/*)
|
(glob_files programs/pkgs/secret/*)
|
||||||
(glob_files programs/pkgs/nosy/*))
|
(glob_files programs/pkgs/nosy/*)
|
||||||
|
(glob_files programs/loose/*))
|
||||||
(action (run ./test_valgrind.exe)))
|
(action (run ./test_valgrind.exe)))
|
||||||
|
|
||||||
; The corpus a fourth time, through the hand-written x86-64 backend, compared
|
; 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/shadowed/*)
|
||||||
(glob_files programs/pkgs/gen/*)
|
(glob_files programs/pkgs/gen/*)
|
||||||
(glob_files programs/pkgs/secret/*)
|
(glob_files programs/pkgs/secret/*)
|
||||||
(glob_files programs/pkgs/nosy/*))
|
(glob_files programs/pkgs/nosy/*)
|
||||||
|
(glob_files programs/loose/*))
|
||||||
(action
|
(action
|
||||||
(setenv SURVEY_STRICT 1
|
(setenv SURVEY_STRICT 1
|
||||||
(setenv SURVEY_QUIET 1
|
(setenv SURVEY_QUIET 1
|
||||||
@ -482,7 +485,8 @@
|
|||||||
(glob_files programs/pkgs/shadowed/*)
|
(glob_files programs/pkgs/shadowed/*)
|
||||||
(glob_files programs/pkgs/gen/*)
|
(glob_files programs/pkgs/gen/*)
|
||||||
(glob_files programs/pkgs/secret/*)
|
(glob_files programs/pkgs/secret/*)
|
||||||
(glob_files programs/pkgs/nosy/*))
|
(glob_files programs/pkgs/nosy/*)
|
||||||
|
(glob_files programs/loose/*))
|
||||||
(action
|
(action
|
||||||
(setenv SURVEY_STRICT 1
|
(setenv SURVEY_STRICT 1
|
||||||
(setenv SURVEY_QUIET 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
|
;;;; 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")
|
(import secret "pkgs/secret")
|
||||||
|
|
||||||
@ -7,4 +8,6 @@
|
|||||||
(print (secret/combine 1 2)) (println "")
|
(print (secret/combine 1 2)) (println "")
|
||||||
(print (secret/twice 3)) (println "")
|
(print (secret/twice 3)) (println "")
|
||||||
(print (secret/via-value 4 5)) (println "")
|
(print (secret/via-value 4 5)) (println "")
|
||||||
|
(print (secret/mixed 6 7)) (println "")
|
||||||
|
(print (secret/mixed-in-do 8 9)) (println "")
|
||||||
0)
|
0)
|
||||||
|
|||||||
@ -5,3 +5,14 @@
|
|||||||
(defn- mix [a i32 b i32] i32 (+ (* a 10) b))
|
(defn- mix [a i32 b i32] i32 (+ (* a 10) b))
|
||||||
|
|
||||||
(defn combine [a i32 b i32] i32 (mix a 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
|
declares it, the package's other file, and as a value handed out from
|
||||||
there. The refusals are with the others, further down. *)
|
there. The refusals are with the others, further down. *)
|
||||||
outputs "a defn- used inside its package" "programs/pkg-private.flan"
|
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.
|
(* 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 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
|
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";
|
"programs/pkg-private-nested.flan" "secret/mix is private to its package";
|
||||||
refuses "and the refusal names the package's directory"
|
refuses "and the refusal names the package's directory"
|
||||||
"programs/pkg-private-call.flan" "pkgs/secret";
|
"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"
|
refuses "one directory under two aliases" "programs/pkg-two-aliases.flan"
|
||||||
"one directory takes one alias";
|
"one directory takes one alias";
|
||||||
(* A ring is refused and the ring is named. The needle is the chain, not
|
(* 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; _ } ->
|
| exception Loc.Error { Loc.dmsg = m; _ } ->
|
||||||
fail "a defn- in the program's own file: %s" 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 ────────────────────────
|
(* ── C-x C-e expands, which it never used to ────────────────────────
|
||||||
[Parse.expr] did not call the expander at all, so an expression typed at
|
[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>
|
<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
|
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
|
directory can call it. For a package imported by naming a single file, the package is
|
||||||
value — is refused at compile time, and the message names the function and the
|
that file alone, and the other files in its directory cannot call it. A use from
|
||||||
directory it belongs to. Only functions have a private form. A single-file package
|
anywhere else — a call, or the name passed as a value — is refused at compile time,
|
||||||
shares its directory with the files beside it, so those files can call its
|
and the message names the function and where it may be used. Code written by one of
|
||||||
<code>defn-</code>s too.</p>
|
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
|
<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
|
compiled into the build, and a file named <code>link</code> lists extra linker
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user