A declared name that starts with $ is refused where it is declared
This commit is contained in:
parent
bf827dc55b
commit
b153f325b8
12
TODO.org
12
TODO.org
@ -598,12 +598,12 @@ back to the call site. Costs about 2µs a call. Rules out putting a =loc= field
|
||||
on the wire, and rules out structural matching of the expansion against the
|
||||
arguments, which can pick the wrong one of two equal subtrees.
|
||||
|
||||
** NEXT A declared name may carry the $ sigil
|
||||
Decided 2026-09-25: refuse =$= at the start of any declared name; the refusal says =$= marks a type variable.
|
||||
=(defn $foo [x i32] i32 ...)= is accepted and =($foo 3)= calls it; so is
|
||||
=(defstruct $S [a i32])=, whose type can then be written nowhere. The character is
|
||||
reserved in every type position and in no name. Refusing it in a declared name
|
||||
would close it properly, and that is a decision about the spelling.
|
||||
** DONE A declared name may carry the $ sigil
|
||||
CLOSED: [2026-09-25]
|
||||
A name that starts with =$= is refused where it is declared — every top-level
|
||||
form, a struct or union field, an enum member, a data case and a =let= name —
|
||||
saying =$= marks a type variable and naming the bare spelling. A parameter was
|
||||
already refused, as a type in a name slot.
|
||||
|
||||
* Checker
|
||||
|
||||
|
||||
64
lib/parse.ml
64
lib/parse.ml
@ -14,6 +14,22 @@ let sym (f : Form.t) =
|
||||
| Sym s -> s
|
||||
| _ -> fail f "expected a name, found %s" (Form.to_string f)
|
||||
|
||||
(* A name something declares. [$] opens a type variable in every type
|
||||
position, so a declared name that starts with one could be written at its
|
||||
definition and at a call and nowhere a type goes — [(defstruct $S ...)] is a
|
||||
type no signature can name. Refused at the declaration, where the fix is. *)
|
||||
let no_sigil (f : Form.t) =
|
||||
match f.v with
|
||||
| Sym s when String.length s > 1 && s.[0] = '$' ->
|
||||
let bare = String.sub s 1 (String.length s - 1) in
|
||||
Loc.failk "parse/sigil-in-name" f.loc
|
||||
"%s cannot be declared: a name does not start with $, which marks a \
|
||||
type variable, as in [x $t]. Name it %s"
|
||||
s bare
|
||||
| _ -> ()
|
||||
|
||||
let dname (f : Form.t) = no_sigil f; sym f
|
||||
|
||||
(* Names for the temporaries this file mints — the value is bound once and
|
||||
everything that needs it reads *that*, so a destructuring pattern over a
|
||||
call calls it once and a short-circuit operand is evaluated once. [~] is a
|
||||
@ -122,7 +138,7 @@ let rec fields (f : Form.t) (items : Form.t list) : Ast.field list =
|
||||
| [] -> []
|
||||
| name :: ty :: rest ->
|
||||
no_pattern name;
|
||||
{ Ast.fname = sym name; fty = texpr ty; floc = name.loc } :: fields f rest
|
||||
{ Ast.fname = dname name; fty = texpr ty; floc = name.loc } :: fields f rest
|
||||
| [ odd ] ->
|
||||
Loc.fail odd.loc "field %s has no type — these come in name/type pairs"
|
||||
(Form.to_string odd)
|
||||
@ -879,7 +895,9 @@ and temp (p : Form.t) (v : Ast.expr) : Ast.expr * Ast.binding =
|
||||
name is what it always was. *)
|
||||
and destructure (p : Form.t) (v : Ast.expr) : Ast.binding list =
|
||||
match p.v with
|
||||
| Sym name -> [ { Ast.bname = name; bty = None; bval = v; bloc = p.loc } ]
|
||||
| Sym name ->
|
||||
no_sigil p;
|
||||
[ { Ast.bname = name; bty = None; bval = v; bloc = p.loc } ]
|
||||
(* The value goes into a temporary first, so it is evaluated once however
|
||||
many names the pattern binds, and so that [(let [{:keys [p]} p] ...)]
|
||||
reads the old [p] rather than the one it is in the middle of rebinding. *)
|
||||
@ -1290,17 +1308,17 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
|
||||
| List ({ v = Sym "defalias"; _ } :: args) ->
|
||||
(match args with
|
||||
| [ n; t ] -> mk (Ast.Defalias (sym n, texpr t))
|
||||
| [ n; t ] -> mk (Ast.Defalias (dname n, texpr t))
|
||||
| _ -> fail f "defalias is (defalias Name Type)")
|
||||
|
||||
| List ({ v = Sym "defstruct"; _ } :: args) ->
|
||||
(match args with
|
||||
| [ n; { v = Vec fs; _ } ] -> mk (Ast.Defstruct (sym n, fields f fs))
|
||||
| [ n; { v = Vec fs; _ } ] -> mk (Ast.Defstruct (dname n, fields f fs))
|
||||
| _ -> fail f "defstruct is (defstruct Name [field Type ...])")
|
||||
|
||||
| List ({ v = Sym "defdata"; _ } :: args) ->
|
||||
(match args with
|
||||
| [ n; { v = Vec vs; _ } ] -> mk (Ast.Defdata (sym n, List.map variant vs))
|
||||
| [ n; { v = Vec vs; _ } ] -> mk (Ast.Defdata (dname n, List.map variant vs))
|
||||
| _ -> fail f "defdata is (defdata Name [(Case [field Type ...]) ...])")
|
||||
|
||||
(* C's union: one storage, as many ways of reading it as there are members.
|
||||
@ -1343,7 +1361,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
[member Type ...]). This reads as a tagged sum — write \
|
||||
(defdata Name [(Case [field Type ...]) ...])")
|
||||
ms;
|
||||
mk (Ast.Defunion (sym n, fields f ms))
|
||||
mk (Ast.Defunion (dname n, fields f ms))
|
||||
| _ -> fail f "defunion is (defunion Name [member Type ...])")
|
||||
|
||||
(* The slot after the parameters is unconditionally the return type. It used
|
||||
@ -1437,7 +1455,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
(Form.to_string ret)
|
||||
in
|
||||
let fwhere, body = constraints body in
|
||||
mk (Ast.Defn { Ast.name = sym n; params = []; praw = Some (pitems ps);
|
||||
mk (Ast.Defn { Ast.name = dname n; params = []; praw = Some (pitems ps);
|
||||
ret = Some rty; fwhere; fbody = body_of body;
|
||||
nloc = n.loc; fprivate })
|
||||
| _ ->
|
||||
@ -1463,7 +1481,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
(match args with
|
||||
| [ n; { v = Vec slots; _ } ] ->
|
||||
mk (Ast.Defclass
|
||||
(sym n,
|
||||
(dname n,
|
||||
List.map
|
||||
(fun (s : Form.t) ->
|
||||
match s.v with
|
||||
@ -1493,7 +1511,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
when if generic then body = [] else body <> [] ->
|
||||
mk ((if generic then (fun fn -> Ast.Defgeneric fn)
|
||||
else fun fn -> Ast.Defmulti fn)
|
||||
{ Ast.name = sym n; params = dyn_params which ps; praw = None;
|
||||
{ Ast.name = dname n; params = dyn_params which ps; praw = None;
|
||||
ret = Some (texpr ret); fwhere = []; fbody = body_of body;
|
||||
nloc = n.loc; fprivate = Ast.Exported })
|
||||
| _ -> fail f "%s" usage)
|
||||
@ -1539,11 +1557,11 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
| { v = Str csym; _ } :: rest ->
|
||||
(match List.rev rest with
|
||||
| [ n; { v = Form.Vec ps; _ } ] ->
|
||||
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
|
||||
mk (mkd { Ast.name = dname n; params = fields f ps; praw = None;
|
||||
ret = None; fwhere = []; fbody = []; nloc = n.loc;
|
||||
fprivate = Ast.Exported } csym)
|
||||
| [ n; { v = Form.Vec ps; _ }; r ] ->
|
||||
mk (mkd { Ast.name = sym n; params = fields f ps; praw = None;
|
||||
mk (mkd { Ast.name = dname n; params = fields f ps; praw = None;
|
||||
ret = Some (texpr r); fwhere = []; fbody = [];
|
||||
nloc = n.loc; fprivate = Ast.Exported } csym)
|
||||
| _ -> fail f "%s" usage)
|
||||
@ -1566,7 +1584,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
| List ({ v = Sym "defenum"; _ } :: args) ->
|
||||
(match args with
|
||||
| [ n; { v = Form.Vec ms; _ } ] ->
|
||||
let ename = sym n in
|
||||
let ename = dname n in
|
||||
(* An enum member is an [i32] at run time. [Shim] lowers the type to
|
||||
int32_t for C's benefit and [Check] builds every member as a
|
||||
[Tast.Int (v, I32)] -- but the reader hands this pass an [int64], so
|
||||
@ -1613,7 +1631,8 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
refusals here and below can be made; neither reaches the AST. *)
|
||||
let rec members next = function
|
||||
| [] -> []
|
||||
| { v = Form.Sym m; loc } :: { v = Form.Int k; _ } :: rest ->
|
||||
| ({ v = Form.Sym m; loc } as mf) :: { v = Form.Int k; _ } :: rest ->
|
||||
no_sigil mf;
|
||||
(* The [let] is load-bearing rather than tidiness. OCaml leaves the
|
||||
evaluation order of [::]'s two operands unspecified and in
|
||||
practice takes the tail first, so an inlined [fits ... k] would
|
||||
@ -1626,7 +1645,8 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
i32 by the time it is incremented, so the sum cannot overflow. *)
|
||||
let k = fits m loc ~explicit:true k in
|
||||
(m, k, true, loc) :: members (Int64.add k 1L) rest
|
||||
| { v = Form.Sym m; loc } :: rest ->
|
||||
| ({ v = Form.Sym m; loc } as mf) :: rest ->
|
||||
no_sigil mf;
|
||||
let next = fits m loc ~explicit:false next in
|
||||
(m, next, false, loc) :: members (Int64.add next 1L) rest
|
||||
| bad :: _ ->
|
||||
@ -1690,11 +1710,11 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
(match args with
|
||||
| [ n; t ] ->
|
||||
let ty, init = defvar3 t in
|
||||
mk (Ast.Defvar (sym n, Some ty, init, kind))
|
||||
mk (Ast.Defvar (dname n, Some ty, init, kind))
|
||||
| [ n; t; { v = Sym "uninit"; _ } ] ->
|
||||
mk (Ast.Defvar (sym n, Some (texpr t), Ast.Uninit, kind))
|
||||
mk (Ast.Defvar (dname n, Some (texpr t), Ast.Uninit, kind))
|
||||
| [ n; t; v ] ->
|
||||
mk (Ast.Defvar (sym n, Some (texpr t), Ast.Init (expr v), kind))
|
||||
mk (Ast.Defvar (dname n, Some (texpr t), Ast.Init (expr v), kind))
|
||||
| _ ->
|
||||
fail f
|
||||
"%s is (%s name Type value?) or (%s name value) — a third element \
|
||||
@ -1733,8 +1753,8 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
|
||||
| List ({ v = Sym "defconst"; _ } :: args) ->
|
||||
(match args with
|
||||
| [ n; v ] -> mk (Ast.Defconst (sym n, None, expr v))
|
||||
| [ n; t; v ] -> mk (Ast.Defconst (sym n, Some (texpr t), expr v))
|
||||
| [ n; v ] -> mk (Ast.Defconst (dname n, None, expr v))
|
||||
| [ n; t; v ] -> mk (Ast.Defconst (dname n, Some (texpr t), expr v))
|
||||
| _ -> fail f "defconst is (defconst name Type? value)")
|
||||
|
||||
(* A macro is an ordinary function, and this is where it becomes one:
|
||||
@ -1764,7 +1784,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
||||
let sg = Expand.params_of ps in
|
||||
let form_t = { Ast.t = Ast.Tname "Form"; tloc = f.loc } in
|
||||
mk (Ast.Defn
|
||||
{ Ast.name = sym n;
|
||||
{ Ast.name = dname n;
|
||||
(* A name the reader cannot produce -- [~] opens an unquote, so
|
||||
no symbol read out of a source file holds one -- which is
|
||||
what keeps the compiler's own parameter out of the way of
|
||||
@ -1844,6 +1864,10 @@ and macro_body (sg : Expand.msig) (body : Form.t list) : Ast.expr list =
|
||||
[ call loc0 (s loc0 "let" :: Form.make (Form.Vec items) loc0 :: body) ]
|
||||
|
||||
and variant (f : Form.t) : Ast.variant =
|
||||
(match f.v with
|
||||
| Sym _ -> no_sigil f
|
||||
| List (n :: _) -> no_sigil n
|
||||
| _ -> ());
|
||||
match f.v with
|
||||
| Sym n -> { Ast.vname = n; vfields = []; vloc = f.loc }
|
||||
| List [ { v = Sym n; _ }; { v = Vec fs; _ } ] ->
|
||||
|
||||
@ -6323,6 +6323,26 @@ let () =
|
||||
(defn main [] () (add2 1 2))"
|
||||
[];
|
||||
|
||||
(* ── A declared name does not start with $ ─────────────────────── *)
|
||||
(* $ marks a type variable in every type position, so a name that starts
|
||||
with one could not be written where a type goes. *)
|
||||
let sigil what src =
|
||||
parse_rejects ("a declared name with a $: " ^ what) src
|
||||
~needle:"a name does not start with $, which marks a type variable"
|
||||
in
|
||||
sigil "defn" "(defn $foo [x i32] i32 (+ x 1))";
|
||||
sigil "defstruct" "(defstruct $S [a i32])";
|
||||
sigil "a struct field" "(defstruct S [$a i32])";
|
||||
sigil "defenum" "(defenum $E [A B])";
|
||||
sigil "an enum member" "(defenum E [A $B])";
|
||||
sigil "defonce" "(defonce $g i32 0)";
|
||||
sigil "defconst" "(defconst $k 3)";
|
||||
sigil "defdata case" "(defdata D [($C [a i32])])";
|
||||
sigil "defmacro" "(defmacro $m [x] x)";
|
||||
sigil "a let binding" "(defn f [] i32 (let [$y 1] y))";
|
||||
parse_rejects "the $ refusal names the bare spelling"
|
||||
"(defn $foo [x i32] i32 x)" ~needle:"Name it foo";
|
||||
|
||||
(* ── The acceptance program checks end to end ──────────────────── *)
|
||||
accepts "calc-me.flan type checks"
|
||||
(In_channel.with_open_bin "../calc-me.flan" In_channel.input_all);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user