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
|
on the wire, and rules out structural matching of the expansion against the
|
||||||
arguments, which can pick the wrong one of two equal subtrees.
|
arguments, which can pick the wrong one of two equal subtrees.
|
||||||
|
|
||||||
** NEXT A declared name may carry the $ sigil
|
** DONE 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.
|
CLOSED: [2026-09-25]
|
||||||
=(defn $foo [x i32] i32 ...)= is accepted and =($foo 3)= calls it; so is
|
A name that starts with =$= is refused where it is declared — every top-level
|
||||||
=(defstruct $S [a i32])=, whose type can then be written nowhere. The character is
|
form, a struct or union field, an enum member, a data case and a =let= name —
|
||||||
reserved in every type position and in no name. Refusing it in a declared name
|
saying =$= marks a type variable and naming the bare spelling. A parameter was
|
||||||
would close it properly, and that is a decision about the spelling.
|
already refused, as a type in a name slot.
|
||||||
|
|
||||||
* Checker
|
* Checker
|
||||||
|
|
||||||
|
|||||||
64
lib/parse.ml
64
lib/parse.ml
@ -14,6 +14,22 @@ let sym (f : Form.t) =
|
|||||||
| Sym s -> s
|
| Sym s -> s
|
||||||
| _ -> fail f "expected a name, found %s" (Form.to_string f)
|
| _ -> 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
|
(* 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
|
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
|
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 ->
|
| name :: ty :: rest ->
|
||||||
no_pattern name;
|
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 ] ->
|
| [ odd ] ->
|
||||||
Loc.fail odd.loc "field %s has no type — these come in name/type pairs"
|
Loc.fail odd.loc "field %s has no type — these come in name/type pairs"
|
||||||
(Form.to_string odd)
|
(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. *)
|
name is what it always was. *)
|
||||||
and destructure (p : Form.t) (v : Ast.expr) : Ast.binding list =
|
and destructure (p : Form.t) (v : Ast.expr) : Ast.binding list =
|
||||||
match p.v with
|
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
|
(* 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] ...)]
|
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. *)
|
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) ->
|
| List ({ v = Sym "defalias"; _ } :: args) ->
|
||||||
(match args with
|
(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)")
|
| _ -> fail f "defalias is (defalias Name Type)")
|
||||||
|
|
||||||
| List ({ v = Sym "defstruct"; _ } :: args) ->
|
| List ({ v = Sym "defstruct"; _ } :: args) ->
|
||||||
(match args with
|
(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 ...])")
|
| _ -> fail f "defstruct is (defstruct Name [field Type ...])")
|
||||||
|
|
||||||
| List ({ v = Sym "defdata"; _ } :: args) ->
|
| List ({ v = Sym "defdata"; _ } :: args) ->
|
||||||
(match args with
|
(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 ...]) ...])")
|
| _ -> fail f "defdata is (defdata Name [(Case [field Type ...]) ...])")
|
||||||
|
|
||||||
(* C's union: one storage, as many ways of reading it as there are members.
|
(* 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 \
|
[member Type ...]). This reads as a tagged sum — write \
|
||||||
(defdata Name [(Case [field Type ...]) ...])")
|
(defdata Name [(Case [field Type ...]) ...])")
|
||||||
ms;
|
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 ...])")
|
| _ -> fail f "defunion is (defunion Name [member Type ...])")
|
||||||
|
|
||||||
(* The slot after the parameters is unconditionally the return type. It used
|
(* 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)
|
(Form.to_string ret)
|
||||||
in
|
in
|
||||||
let fwhere, body = constraints body 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;
|
ret = Some rty; fwhere; fbody = body_of body;
|
||||||
nloc = n.loc; fprivate })
|
nloc = n.loc; fprivate })
|
||||||
| _ ->
|
| _ ->
|
||||||
@ -1463,7 +1481,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
(match args with
|
(match args with
|
||||||
| [ n; { v = Vec slots; _ } ] ->
|
| [ n; { v = Vec slots; _ } ] ->
|
||||||
mk (Ast.Defclass
|
mk (Ast.Defclass
|
||||||
(sym n,
|
(dname n,
|
||||||
List.map
|
List.map
|
||||||
(fun (s : Form.t) ->
|
(fun (s : Form.t) ->
|
||||||
match s.v with
|
match s.v with
|
||||||
@ -1493,7 +1511,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
when if generic then body = [] else body <> [] ->
|
when if generic then body = [] else body <> [] ->
|
||||||
mk ((if generic then (fun fn -> Ast.Defgeneric fn)
|
mk ((if generic then (fun fn -> Ast.Defgeneric fn)
|
||||||
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 = dname 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 = Ast.Exported })
|
nloc = n.loc; fprivate = Ast.Exported })
|
||||||
| _ -> fail f "%s" usage)
|
| _ -> fail f "%s" usage)
|
||||||
@ -1539,11 +1557,11 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
| { v = Str csym; _ } :: rest ->
|
| { v = Str csym; _ } :: rest ->
|
||||||
(match List.rev rest with
|
(match List.rev rest with
|
||||||
| [ 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 = dname n; params = fields f ps; praw = None;
|
||||||
ret = None; fwhere = []; fbody = []; nloc = n.loc;
|
ret = None; fwhere = []; fbody = []; nloc = n.loc;
|
||||||
fprivate = Ast.Exported } 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 = dname n; params = fields f ps; praw = None;
|
||||||
ret = Some (texpr r); fwhere = []; fbody = [];
|
ret = Some (texpr r); fwhere = []; fbody = [];
|
||||||
nloc = n.loc; fprivate = Ast.Exported } csym)
|
nloc = n.loc; fprivate = Ast.Exported } csym)
|
||||||
| _ -> fail f "%s" usage)
|
| _ -> fail f "%s" usage)
|
||||||
@ -1566,7 +1584,7 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
| List ({ v = Sym "defenum"; _ } :: args) ->
|
| List ({ v = Sym "defenum"; _ } :: args) ->
|
||||||
(match args with
|
(match args with
|
||||||
| [ n; { v = Form.Vec ms; _ } ] ->
|
| [ 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
|
(* 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
|
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
|
[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. *)
|
refusals here and below can be made; neither reaches the AST. *)
|
||||||
let rec members next = function
|
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
|
(* The [let] is load-bearing rather than tidiness. OCaml leaves the
|
||||||
evaluation order of [::]'s two operands unspecified and in
|
evaluation order of [::]'s two operands unspecified and in
|
||||||
practice takes the tail first, so an inlined [fits ... k] would
|
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. *)
|
i32 by the time it is incremented, so the sum cannot overflow. *)
|
||||||
let k = fits m loc ~explicit:true k in
|
let k = fits m loc ~explicit:true k in
|
||||||
(m, k, true, loc) :: members (Int64.add k 1L) rest
|
(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
|
let next = fits m loc ~explicit:false next in
|
||||||
(m, next, false, loc) :: members (Int64.add next 1L) rest
|
(m, next, false, loc) :: members (Int64.add next 1L) rest
|
||||||
| bad :: _ ->
|
| bad :: _ ->
|
||||||
@ -1690,11 +1710,11 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
(match args with
|
(match args with
|
||||||
| [ n; t ] ->
|
| [ n; t ] ->
|
||||||
let ty, init = defvar3 t in
|
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"; _ } ] ->
|
| [ 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 ] ->
|
| [ 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
|
fail f
|
||||||
"%s is (%s name Type value?) or (%s name value) — a third element \
|
"%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) ->
|
| List ({ v = Sym "defconst"; _ } :: args) ->
|
||||||
(match args with
|
(match args with
|
||||||
| [ n; v ] -> mk (Ast.Defconst (sym n, None, expr v))
|
| [ n; v ] -> mk (Ast.Defconst (dname n, None, expr v))
|
||||||
| [ n; t; v ] -> mk (Ast.Defconst (sym n, Some (texpr t), expr v))
|
| [ n; t; v ] -> mk (Ast.Defconst (dname n, Some (texpr t), expr v))
|
||||||
| _ -> fail f "defconst is (defconst name Type? value)")
|
| _ -> fail f "defconst is (defconst name Type? value)")
|
||||||
|
|
||||||
(* A macro is an ordinary function, and this is where it becomes one:
|
(* 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 sg = Expand.params_of ps in
|
||||||
let form_t = { Ast.t = Ast.Tname "Form"; tloc = f.loc } in
|
let form_t = { Ast.t = Ast.Tname "Form"; tloc = f.loc } in
|
||||||
mk (Ast.Defn
|
mk (Ast.Defn
|
||||||
{ Ast.name = sym n;
|
{ Ast.name = dname n;
|
||||||
(* A name the reader cannot produce -- [~] opens an unquote, so
|
(* A name the reader cannot produce -- [~] opens an unquote, so
|
||||||
no symbol read out of a source file holds one -- which is
|
no symbol read out of a source file holds one -- which is
|
||||||
what keeps the compiler's own parameter out of the way of
|
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) ]
|
[ call loc0 (s loc0 "let" :: Form.make (Form.Vec items) loc0 :: body) ]
|
||||||
|
|
||||||
and variant (f : Form.t) : Ast.variant =
|
and variant (f : Form.t) : Ast.variant =
|
||||||
|
(match f.v with
|
||||||
|
| Sym _ -> no_sigil f
|
||||||
|
| List (n :: _) -> no_sigil n
|
||||||
|
| _ -> ());
|
||||||
match f.v with
|
match f.v with
|
||||||
| Sym n -> { Ast.vname = n; vfields = []; vloc = f.loc }
|
| Sym n -> { Ast.vname = n; vfields = []; vloc = f.loc }
|
||||||
| List [ { v = Sym n; _ }; { v = Vec fs; _ } ] ->
|
| List [ { v = Sym n; _ }; { v = Vec fs; _ } ] ->
|
||||||
|
|||||||
@ -6323,6 +6323,26 @@ let () =
|
|||||||
(defn main [] () (add2 1 2))"
|
(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 ──────────────────── *)
|
(* ── The acceptance program checks end to end ──────────────────── *)
|
||||||
accepts "calc-me.flan type checks"
|
accepts "calc-me.flan type checks"
|
||||||
(In_channel.with_open_bin "../calc-me.flan" In_channel.input_all);
|
(In_channel.with_open_bin "../calc-me.flan" In_channel.input_all);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user