The defvar follow-ups, the spellings other languages use, and two register warts
The four the defvar review left behind, plus the two the author's dogfooding notes name. A three-element defvar that is neither a type nor a value gets a paragraph about the fork it stands at, and the paragraph is right for the name that genuinely could have been either. Three names cannot: a data case, which is a third thing with its own spelling; a name another language uses for a type this one has; and a plain type typo, where a confident one-edit suggestion was turning a line into four. Each answers first now. A bracket form never reaches that fork at all — the parser gives it the type reading outright — so a value name inside one landed in [resolve_name] and came back as a lecture about generic code. Both readings at the element that decided it, and the dyn spelling it offers is checked to be a real form. [int] is two edits from [i32] and so outside the one-edit net, correctly: two edits is a guess. But the name is not a guess, it is what four other languages call the default integer, so a short list answers it by name. Nothing goes on that list without one honest answer — [char] and [void] are off it, and the comment says why. A parameter called [i] is not a mistyped [i8]. The machine types size themselves in the name, so a typo keeps the digits and a parameter name has none; that is the rule that stopped (defn idx [v i] dyn ...) being refused. A type written in a two-element defconst was reported as an unknown name, because that form has no type slot and the brackets read as an array literal. A type name inside one is unambiguous — a type and a value cannot share a name here — so it says what happened and names defvar. Two register warts alongside: no message cites a repo filename at the reader any more (plan.org in three, spec-memory.md in one).
This commit is contained in:
parent
db2f6c69b9
commit
8add0093ba
186
lib/check.ml
186
lib/check.ml
@ -228,6 +228,35 @@ let one_edit a b =
|
|||||||
value goes was not a mistyped struct. *)
|
value goes was not a mistyped struct. *)
|
||||||
let nearest cands n = List.find_opt (fun c -> c <> n && one_edit n c) cands
|
let nearest cands n = List.find_opt (fun c -> c <> n && one_edit n c) cands
|
||||||
|
|
||||||
|
(* What the last language called it. [int] is two edits from [i32] and so is
|
||||||
|
outside [one_edit]'s net, which is right — two edits is a guess — but the
|
||||||
|
name is not a guess at all: it is what C, Java, Go and Python spell the
|
||||||
|
default integer, and somebody writing it here has not mistyped anything,
|
||||||
|
they have not yet learned that this language sizes its integers in the
|
||||||
|
name. Without this list [int] falls through to the lowercase arm of
|
||||||
|
[resolve_name] and is reported as generic code over a type variable, which
|
||||||
|
is a sentence about a feature the reader was not reaching for.
|
||||||
|
|
||||||
|
Short on purpose, and only names with one honest answer. [char] is not
|
||||||
|
here: C's is a byte, Java's is a UTF-16 unit and Rust's is a scalar value,
|
||||||
|
and this language has [u8] and rune functions, so there is nothing to
|
||||||
|
translate it to in three words. Nor [void]: it is a return type and the
|
||||||
|
answer there is the shape [()], which is [parse]'s message to give and not
|
||||||
|
this one's. *)
|
||||||
|
let foreign_spelling = function
|
||||||
|
| "int" | "integer" -> Some "i32"
|
||||||
|
| "uint" | "unsigned" -> Some "u32"
|
||||||
|
| "long" -> Some "i64"
|
||||||
|
| "ulong" -> Some "u64"
|
||||||
|
| "short" -> Some "i16"
|
||||||
|
| "ushort" -> Some "u16"
|
||||||
|
| "byte" -> Some "u8"
|
||||||
|
| "float" -> Some "f32"
|
||||||
|
| "double" -> Some "f64"
|
||||||
|
| "boolean" -> Some "bool"
|
||||||
|
| "str" -> Some "string"
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
(* The builtin names, for the did-you-mean at a call — [prinltn] is a typo for
|
(* The builtin names, for the did-you-mean at a call — [prinltn] is a typo for
|
||||||
[println], and [println] is not in any table the checker keeps, it is an arm
|
[println], and [println] is not in any table the checker keeps, it is an arm
|
||||||
of the call dispatch. The full [builtins] table is a long way below this
|
of the call dispatch. The full [builtins] table is a long way below this
|
||||||
@ -469,7 +498,10 @@ let branch ctx f =
|
|||||||
(* ── Type resolution ───────────────────────────────────────────────── *)
|
(* ── Type resolution ───────────────────────────────────────────────── *)
|
||||||
|
|
||||||
let unimplemented loc what milestone =
|
let unimplemented loc what milestone =
|
||||||
fail loc "%s is not implemented yet — milestone %d (see plan.org)"
|
(* No repo filename in a message. Somebody meeting this wants to know that
|
||||||
|
the thing is not there yet and roughly how far off it is; where the
|
||||||
|
schedule is written down is the compiler's business, not theirs. *)
|
||||||
|
fail loc "%s is not implemented yet — it is milestone %d work"
|
||||||
what milestone
|
what milestone
|
||||||
|
|
||||||
(* ── where predicates ──────────────────────────────────────────────────
|
(* ── where predicates ──────────────────────────────────────────────────
|
||||||
@ -801,6 +833,9 @@ and resolve_name env ~seen loc n =
|
|||||||
(* A typo in a primitive is lowercase too, and the type-variable rule
|
(* A typo in a primitive is lowercase too, and the type-variable rule
|
||||||
below would otherwise report [f65] as unimplemented generics and send
|
below would otherwise report [f65] as unimplemented generics and send
|
||||||
you to plan.org instead of to the character you mistyped. *)
|
you to plan.org instead of to the character you mistyped. *)
|
||||||
|
| _ when foreign_spelling n <> None ->
|
||||||
|
Loc.failk "check/unknown-type" loc "unknown type %s — Flan spells it %s"
|
||||||
|
n (Option.get (foreign_spelling n))
|
||||||
| _ when near_miss env n <> None ->
|
| _ when near_miss env n <> None ->
|
||||||
Loc.failk "check/unknown-type" loc "unknown type %s — did you mean %s?" n
|
Loc.failk "check/unknown-type" loc "unknown type %s — did you mean %s?" n
|
||||||
(Option.get (near_miss env n))
|
(Option.get (near_miss env n))
|
||||||
@ -879,7 +914,22 @@ let is_type_name env n =
|
|||||||
that it was meant to be one. That case is the feature working as specified,
|
that it was meant to be one. That case is the feature working as specified,
|
||||||
and it is the residual the parent owns. *)
|
and it is the residual the parent owns. *)
|
||||||
let dyn_param_or_typo env n loc =
|
let dyn_param_or_typo env n loc =
|
||||||
match near_miss env n with
|
(* [(defn idx [v i] dyn ...)] is two dyn parameters, and [i] is one edit
|
||||||
|
from [i8], so the did-you-mean used to accuse a perfectly ordinary
|
||||||
|
parameter name of being a mistyped type. What separates the two is the
|
||||||
|
digits: this language sizes its machine types in the name, so a typo in
|
||||||
|
one keeps them — [f65] for [f64], [i33] for [i32] — while [i], [v], [n]
|
||||||
|
and [x] carry none and are what parameters are actually called. A name
|
||||||
|
with no digit, one edit from a type that has one, is a parameter; the
|
||||||
|
suggestion is dropped and the dyn reading stands, which is the reading
|
||||||
|
the writer meant. *)
|
||||||
|
let has_digit s = String.exists (fun c -> c >= '0' && c <= '9') s in
|
||||||
|
let suggestion =
|
||||||
|
match near_miss env n with
|
||||||
|
| Some m when has_digit m && not (has_digit n) -> None
|
||||||
|
| m -> m
|
||||||
|
in
|
||||||
|
match suggestion with
|
||||||
| Some m ->
|
| Some m ->
|
||||||
Loc.failk "check/unknown-type" loc
|
Loc.failk "check/unknown-type" loc
|
||||||
"unknown type %s — did you mean %s? A parameter with no type is dyn, so \
|
"unknown type %s — did you mean %s? A parameter with no type is dyn, so \
|
||||||
@ -983,7 +1033,33 @@ let defvar_reads_as_type env (t : Ast.texpr) =
|
|||||||
so a message naming only one of them would send a reader looking for the
|
so a message naming only one of them would send a reader looking for the
|
||||||
wrong mistake. Both readings, both spellings, and the near miss over the
|
wrong mistake. Both readings, both spellings, and the near miss over the
|
||||||
value names as well as the type names. *)
|
value names as well as the type names. *)
|
||||||
let defvar_neither env loc gname n ~values =
|
(* Both readings, and the paragraph that explains them — but only when both
|
||||||
|
readings really are open. Three things get in ahead of it, because each one
|
||||||
|
knows which of the two the writer meant and the paragraph would bury that
|
||||||
|
under a lecture about a fork they are not standing at:
|
||||||
|
|
||||||
|
a case name, which is a third thing entirely and has its own spelling; a
|
||||||
|
name another language spells for a type this one has under a different
|
||||||
|
name; and a plain type typo, where a confident one-edit suggestion turns a
|
||||||
|
one-line answer into four lines of unrelated reading. The paragraph is for
|
||||||
|
the name that genuinely could have been either and is neither. *)
|
||||||
|
let defvar_neither env loc gname n ~values ~cases =
|
||||||
|
(match List.assoc_opt n cases with
|
||||||
|
| Some dname ->
|
||||||
|
Loc.failk "check/defvar-case-not-type" loc
|
||||||
|
"%s is a case of the data type %s, and a case is not a type of its \
|
||||||
|
own — the global's type is the data type: (defvar %s %s). Assign the \
|
||||||
|
case you want, as (set %s (%s.%s {.field value ...}))"
|
||||||
|
n dname gname dname gname dname n
|
||||||
|
| None -> ());
|
||||||
|
(match foreign_spelling n with
|
||||||
|
| Some m ->
|
||||||
|
Loc.failk "check/unknown-type" loc "unknown type %s — Flan spells it %s" n m
|
||||||
|
| None -> ());
|
||||||
|
(match near_miss env n with
|
||||||
|
| Some m ->
|
||||||
|
Loc.failk "check/unknown-type" loc "unknown type %s — did you mean %s?" n m
|
||||||
|
| None -> ());
|
||||||
let hint =
|
let hint =
|
||||||
match near_miss env ~also:values n with
|
match near_miss env ~also:values n with
|
||||||
| Some m -> Printf.sprintf " — did you mean %s?" m
|
| Some m -> Printf.sprintf " — did you mean %s?" m
|
||||||
@ -1001,6 +1077,21 @@ let defvar_neither env loc gname n ~values =
|
|||||||
asked "is this name declared at all", so a global that is itself a defvar
|
asked "is this name declared at all", so a global that is itself a defvar
|
||||||
still undecided belongs on it: what it resolves to is the next pass's
|
still undecided belongs on it: what it resolves to is the next pass's
|
||||||
question, not this one's. *)
|
question, not this one's. *)
|
||||||
|
(* Case name -> the data type it belongs to, read off the declarations rather
|
||||||
|
than out of [env.cases]: this runs inside [collect], which has registered
|
||||||
|
the data type *names* by here but not resolved their cases, so the table
|
||||||
|
would be empty. Last writer wins, exactly as [env.cases] does, and for the
|
||||||
|
same reason — this is only ever asked "what is this a case of", and two
|
||||||
|
data types may share a case name. *)
|
||||||
|
let case_owners (decls : Ast.decl list) =
|
||||||
|
List.concat_map
|
||||||
|
(fun (d : Ast.decl) ->
|
||||||
|
match d.Ast.d with
|
||||||
|
| Ast.Defdata (dn, vs) ->
|
||||||
|
List.map (fun (v : Ast.variant) -> (v.Ast.vname, dn)) vs
|
||||||
|
| _ -> [])
|
||||||
|
decls
|
||||||
|
|
||||||
let value_names env (decls : Ast.decl list) =
|
let value_names env (decls : Ast.decl list) =
|
||||||
let declared =
|
let declared =
|
||||||
List.filter_map
|
List.filter_map
|
||||||
@ -1022,18 +1113,62 @@ let value_names env (decls : Ast.decl list) =
|
|||||||
dyn reading is rewritten into exactly [(defvar x dyn <expr>)], which is the
|
dyn reading is rewritten into exactly [(defvar x dyn <expr>)], which is the
|
||||||
whole of "it lowers to the same thing": the startup lifting, the re-run
|
whole of "it lowers to the same thing": the startup lifting, the re-run
|
||||||
guard and the collector root are the ones that form already had. *)
|
guard and the collector root are the ones that form already had. *)
|
||||||
|
(* A bracket form whose element names a value. [(defvar g [a b])] parses as a
|
||||||
|
type and stays one — type wins wherever there is a type reading, which is
|
||||||
|
the rule — so the element had to name an element type, and [b] names a
|
||||||
|
defvar. Left alone this reaches [resolve_name], where a lowercase name that
|
||||||
|
is no type is a type variable, and the answer is a paragraph about generic
|
||||||
|
code the writer was not asking for.
|
||||||
|
|
||||||
|
Both readings, and both spellings, at the element that decided it. The dyn
|
||||||
|
spelling is the one that actually works: [(defvar g dyn [a b])] is a dyn
|
||||||
|
global holding a vector, which is what the brackets meant to whoever wrote
|
||||||
|
them. *)
|
||||||
|
let rec bracket_value_element env values (t : Ast.texpr) =
|
||||||
|
let elem (e : Ast.texpr) =
|
||||||
|
match e.Ast.t with
|
||||||
|
| Ast.Tname n when (not (is_type_name env n)) && List.mem n values ->
|
||||||
|
Some (n, e.Ast.tloc)
|
||||||
|
| _ -> bracket_value_element env values e
|
||||||
|
in
|
||||||
|
match t.Ast.t with
|
||||||
|
| Ast.Tslice e -> elem e
|
||||||
|
| Ast.Tarray (_, e) -> elem e
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
let settle_defvars env (decls : Ast.decl list) : Ast.decl list =
|
let settle_defvars env (decls : Ast.decl list) : Ast.decl list =
|
||||||
let values = lazy (value_names env decls) in
|
let values = lazy (value_names env decls) in
|
||||||
|
let cases = lazy (case_owners decls) in
|
||||||
|
(* A bracket form never reaches the fork below: [Parse.defvar3] gives it
|
||||||
|
[Zeroed] outright, because a bracket that parses as a type has no second
|
||||||
|
reading to carry. So the element check runs on both, and it is the only
|
||||||
|
thing the [Zeroed] arm does. *)
|
||||||
|
let brackets gname (t : Ast.texpr) =
|
||||||
|
match bracket_value_element env (Lazy.force values) t with
|
||||||
|
| Some (v, vloc) ->
|
||||||
|
Loc.failk "check/defvar-bracket-element-is-a-value" vloc
|
||||||
|
"%s names a value, not a type, and the brackets around it were read \
|
||||||
|
as a type — a defvar's third element is a type wherever there is a \
|
||||||
|
type reading, so %s had to be the element type. Write a type there \
|
||||||
|
for a zeroed global, or put dyn in front of the same brackets — \
|
||||||
|
(defvar %s dyn ...) — for a dyn global holding the vector you wrote"
|
||||||
|
v v gname
|
||||||
|
| None -> ()
|
||||||
|
in
|
||||||
List.map
|
List.map
|
||||||
(fun (d : Ast.decl) ->
|
(fun (d : Ast.decl) ->
|
||||||
match d.Ast.d with
|
match d.Ast.d with
|
||||||
|
| Ast.Defvar (n, Some t, Ast.Zeroed) -> brackets n t; d
|
||||||
| Ast.Defvar (n, Some t, Ast.Ambiguous e) ->
|
| Ast.Defvar (n, Some t, Ast.Ambiguous e) ->
|
||||||
if defvar_reads_as_type env t then
|
if defvar_reads_as_type env t then begin
|
||||||
|
brackets n t;
|
||||||
{ d with Ast.d = Ast.Defvar (n, Some t, Ast.Zeroed) }
|
{ d with Ast.d = Ast.Defvar (n, Some t, Ast.Zeroed) }
|
||||||
|
end
|
||||||
else begin
|
else begin
|
||||||
(match t.Ast.t with
|
(match t.Ast.t with
|
||||||
| Ast.Tname s when not (List.mem s (Lazy.force values)) ->
|
| Ast.Tname s when not (List.mem s (Lazy.force values)) ->
|
||||||
defvar_neither env t.Ast.tloc n s ~values:(Lazy.force values)
|
defvar_neither env t.Ast.tloc n s ~values:(Lazy.force values)
|
||||||
|
~cases:(Lazy.force cases)
|
||||||
| _ -> ());
|
| _ -> ());
|
||||||
let dyn = { Ast.t = Ast.Tname "dyn"; tloc = t.Ast.tloc } in
|
let dyn = { Ast.t = Ast.Tname "dyn"; tloc = t.Ast.tloc } in
|
||||||
{ d with Ast.d = Ast.Defvar (n, Some dyn, Ast.Init e) }
|
{ d with Ast.d = Ast.Defvar (n, Some dyn, Ast.Init e) }
|
||||||
@ -4337,8 +4472,8 @@ and check_place ctx loc (p : Ast.place) : Tast.place * Types.t =
|
|||||||
| Some b ->
|
| Some b ->
|
||||||
if not b.assignable then
|
if not b.assignable then
|
||||||
fail loc
|
fail loc
|
||||||
"%s is a parameter, and parameters are not assignable places \
|
"%s is a parameter, and a parameter is not a place you can assign \
|
||||||
(spec-memory.md) — bind a local with let" name;
|
to — bind a local with let" name;
|
||||||
Tast.Plocal b.slot, b.bty
|
Tast.Plocal b.slot, b.bty
|
||||||
| None ->
|
| None ->
|
||||||
match Hashtbl.find_opt ctx.env.globals name with
|
match Hashtbl.find_opt ctx.env.globals name with
|
||||||
@ -4918,12 +5053,12 @@ and named_call ctx ~want loc name args =
|
|||||||
(match name with
|
(match name with
|
||||||
| "=" | "!=" ->
|
| "=" | "!=" ->
|
||||||
fail loc
|
fail loc
|
||||||
"%s compares machine numbers, enums and strings; %s has no \
|
"%s compares machine numbers, enums and strings, and %s is none of \
|
||||||
built-in equality (plan.org, Types)" name (Types.to_string a.Tast.ty)
|
those" name (Types.to_string a.Tast.ty)
|
||||||
| _ ->
|
| _ ->
|
||||||
fail loc
|
fail loc
|
||||||
"%s orders machine numbers and enums; %s has no built-in ordering \
|
"%s orders machine numbers and enums, and %s is neither" name
|
||||||
(plan.org, Types)" name (Types.to_string a.Tast.ty));
|
(Types.to_string a.Tast.ty));
|
||||||
prim p Types.Bool [ a; b ]
|
prim p Types.Bool [ a; b ]
|
||||||
end
|
end
|
||||||
| "not" ->
|
| "not" ->
|
||||||
@ -7191,6 +7326,33 @@ let rec const_int env (e : Ast.expr) : int64 option =
|
|||||||
(const_int env x) (y :: rest)
|
(const_int env x) (y :: rest)
|
||||||
| _ -> None
|
| _ -> None
|
||||||
|
|
||||||
|
(* [(defconst grid [rows [cols u8]])]. A two-element defconst has no type slot
|
||||||
|
— the second form is always a value — so the brackets were read as an array
|
||||||
|
*literal* and [u8] as a name in it, and the refusal that came out was
|
||||||
|
"unknown name u8", which sends the reader to look for a missing definition
|
||||||
|
of something the language has had all along.
|
||||||
|
|
||||||
|
A type name inside an array literal is unambiguous evidence, because a type
|
||||||
|
and a value cannot share a name: [collect]'s claimed table is over every
|
||||||
|
declaration kind there is. So finding one means the whole form was meant as
|
||||||
|
a type, and the form that takes one is [defvar]. *)
|
||||||
|
let rec defconst_type_shaped env gname (v : Ast.expr) =
|
||||||
|
match v.Ast.e with
|
||||||
|
| Ast.Arr items ->
|
||||||
|
List.iter
|
||||||
|
(fun (i : Ast.expr) ->
|
||||||
|
match i.Ast.e with
|
||||||
|
| Ast.Var n when is_type_name env n ->
|
||||||
|
Loc.failk "check/defconst-is-a-type" i.Ast.loc
|
||||||
|
"%s is a type, and this is a value: a two-element defconst has no \
|
||||||
|
type slot, so the brackets around it were read as an array \
|
||||||
|
literal and %s as a name in it. A global declared by its type is \
|
||||||
|
a defvar — write (defvar %s ...) with the same brackets"
|
||||||
|
n n gname
|
||||||
|
| _ -> defconst_type_shaped env gname i)
|
||||||
|
items
|
||||||
|
| _ -> ()
|
||||||
|
|
||||||
let collect env (decls : Ast.decl list) =
|
let collect env (decls : Ast.decl list) =
|
||||||
(* One pass over every declaration kind before any of the others, because
|
(* One pass over every declaration kind before any of the others, because
|
||||||
the tables below are per-kind — structs, data types, aliases, enums, functions
|
the tables below are per-kind — structs, data types, aliases, enums, functions
|
||||||
@ -7493,7 +7655,9 @@ let collect env (decls : Ast.decl list) =
|
|||||||
Hashtbl.replace env.globals n (ty, false)
|
Hashtbl.replace env.globals n (ty, false)
|
||||||
| Ast.Defconst (n, Some t, _) ->
|
| Ast.Defconst (n, Some t, _) ->
|
||||||
Hashtbl.replace env.globals n (resolve env t, true)
|
Hashtbl.replace env.globals n (resolve env t, true)
|
||||||
| Ast.Defconst (n, None, v) -> untyped := (n, v) :: !untyped
|
| Ast.Defconst (n, None, v) ->
|
||||||
|
defconst_type_shaped env n v;
|
||||||
|
untyped := (n, v) :: !untyped
|
||||||
(* [Classes.expand] ran at the top of [build_program] and left none of
|
(* [Classes.expand] ran at the top of [build_program] and left none of
|
||||||
these behind, the way [Shim.expand] leaves no [declare-c] behind. A
|
these behind, the way [Shim.expand] leaves no [declare-c] behind. A
|
||||||
driver that assembled a declaration list and skipped that pass would
|
driver that assembled a declaration list and skipped that pass would
|
||||||
|
|||||||
@ -1661,7 +1661,7 @@ let () =
|
|||||||
accepts "a local is assignable"
|
accepts "a local is assignable"
|
||||||
"(defn f [] i32 (let [x 1] (set x 2) x))";
|
"(defn f [] i32 (let [x 1] (set x 2) x))";
|
||||||
rejects_check "a parameter is not assignable"
|
rejects_check "a parameter is not assignable"
|
||||||
"(defn f [x i32] () (set x 2))" ~needle:"parameters are not assignable";
|
"(defn f [x i32] () (set x 2))" ~needle:"a parameter is not a place you can assign to";
|
||||||
rejects_check "a constant is not assignable"
|
rejects_check "a constant is not assignable"
|
||||||
"(defconst k 1) (defn f [] () (set k 2))" ~needle:"is a constant";
|
"(defconst k 1) (defn f [] () (set k 2))" ~needle:"is a constant";
|
||||||
accepts "addr of a local gives a pointer"
|
accepts "addr of a local gives a pointer"
|
||||||
@ -1924,13 +1924,13 @@ let () =
|
|||||||
accepts "typed = on strings" "(defn f [] bool (= \"a\" \"b\"))";
|
accepts "typed = on strings" "(defn f [] bool (= \"a\" \"b\"))";
|
||||||
accepts "typed != on strings" "(defn f [] bool (!= \"a\" \"b\"))";
|
accepts "typed != on strings" "(defn f [] bool (!= \"a\" \"b\"))";
|
||||||
rejects_check "no built-in < on strings"
|
rejects_check "no built-in < on strings"
|
||||||
"(defn f [] bool (< \"a\" \"b\"))" ~needle:"no built-in ordering";
|
"(defn f [] bool (< \"a\" \"b\"))" ~needle:"orders machine numbers and enums";
|
||||||
rejects_check "no built-in <= on strings"
|
rejects_check "no built-in <= on strings"
|
||||||
"(defn f [] bool (<= \"a\" \"b\"))" ~needle:"no built-in ordering";
|
"(defn f [] bool (<= \"a\" \"b\"))" ~needle:"orders machine numbers and enums";
|
||||||
rejects_check "no built-in > on strings"
|
rejects_check "no built-in > on strings"
|
||||||
"(defn f [] bool (> \"a\" \"b\"))" ~needle:"no built-in ordering";
|
"(defn f [] bool (> \"a\" \"b\"))" ~needle:"orders machine numbers and enums";
|
||||||
rejects_check "no built-in >= on strings"
|
rejects_check "no built-in >= on strings"
|
||||||
"(defn f [] bool (>= \"a\" \"b\"))" ~needle:"no built-in ordering";
|
"(defn f [] bool (>= \"a\" \"b\"))" ~needle:"orders machine numbers and enums";
|
||||||
(* (Vec T) is built. What is still refused is the arity: one element type,
|
(* (Vec T) is built. What is still refused is the arity: one element type,
|
||||||
and a near-miss there would otherwise resolve to a type variable and come
|
and a near-miss there would otherwise resolve to a type variable and come
|
||||||
back as generics. *)
|
back as generics. *)
|
||||||
@ -2069,6 +2069,57 @@ let () =
|
|||||||
rejects_check "the near miss is over the value names as well as the types"
|
rejects_check "the near miss is over the value names as well as the types"
|
||||||
"(defvar score i64 1) (defvar total scor) (defn f [] ())"
|
"(defvar score i64 1) (defvar total scor) (defn f [] ())"
|
||||||
~needle:"Nothing named scor is declared as either — did you mean score?";
|
~needle:"Nothing named scor is declared as either — did you mean score?";
|
||||||
|
(* Three things that know which of the two readings was meant, and get in
|
||||||
|
ahead of the paragraph rather than being buried under it. A paragraph
|
||||||
|
about a fork the reader is not standing at is worse than a line. *)
|
||||||
|
rejects_check "a plain type typo keeps the short answer"
|
||||||
|
"(defvar total i33) (defn f [] ())"
|
||||||
|
~needle:"unknown type i33 — did you mean i32?";
|
||||||
|
rejects_check "and another language's spelling is answered by name"
|
||||||
|
"(defvar total int) (defn f [] ())"
|
||||||
|
~needle:"unknown type int — Flan spells it i32";
|
||||||
|
rejects_check "a data case is not a type, and says what is"
|
||||||
|
"(defdata Shape [(Circle [r f64])]) (defvar g Circle) (defn f [] ())"
|
||||||
|
~needle:"Circle is a case of the data type Shape, and a case is not a \
|
||||||
|
type of its own — the global's type is the data type: (defvar g \
|
||||||
|
Shape). Assign the case you want, as (set g (Shape.Circle \
|
||||||
|
{.field value ...}))";
|
||||||
|
(* A bracket form never reaches that fork — the parser gives it the type
|
||||||
|
reading outright — so a value name inside one used to land in
|
||||||
|
[resolve_name] and come back as a lecture about generic code. Both
|
||||||
|
readings at the element that decided it, and the dyn spelling is the one
|
||||||
|
that works. *)
|
||||||
|
rejects_check "a bracket type whose element names a value says both readings"
|
||||||
|
"(defvar a i64 1) (defvar b i64 2) (defvar g [a b]) (defn f [] ())"
|
||||||
|
~needle:"b names a value, not a type, and the brackets around it were \
|
||||||
|
read as a type";
|
||||||
|
rejects_check "and names the dyn spelling that does work"
|
||||||
|
"(defvar a i64 1) (defvar b i64 2) (defvar g [a b]) (defn f [] ())"
|
||||||
|
~needle:"put dyn in front of the same brackets — (defvar g dyn ...)";
|
||||||
|
accepts "which is a real form"
|
||||||
|
"(defvar a i64 1) (defvar b i64 2) (defvar g dyn [a b]) (defn f [] ())";
|
||||||
|
|
||||||
|
(* defconst's two-element form has no type slot, so a type written in one
|
||||||
|
was read as a name in an array literal and reported as unknown. It is
|
||||||
|
unambiguous evidence: a type and a value cannot share a name here. *)
|
||||||
|
rejects_check "a type in a two-element defconst names defvar"
|
||||||
|
"(defconst rows 4) (defconst cols 4) (defconst grid [rows [cols u8]]) \
|
||||||
|
(defn f [] ())"
|
||||||
|
~needle:"u8 is a type, and this is a value: a two-element defconst has no \
|
||||||
|
type slot";
|
||||||
|
accepts "and the defvar it names is the form that works"
|
||||||
|
"(defconst rows 4) (defconst cols 4) (defvar grid [rows [cols u8]]) \
|
||||||
|
(defn f [] ())";
|
||||||
|
accepts "an ordinary array constant is untouched" "(defconst xs [1 2 3])";
|
||||||
|
|
||||||
|
(* A parameter name is not a mistyped type. This language sizes its machine
|
||||||
|
types in the name, so a typo in one keeps the digits and a parameter
|
||||||
|
called [i] or [n] has none — which is the whole of the rule that stopped
|
||||||
|
[(defn idx [v i] dyn ...)] being refused. *)
|
||||||
|
accepts "a short parameter name is not a mistyped type"
|
||||||
|
"(defn idx [v i] dyn v)";
|
||||||
|
rejects_check "but a mistyped machine type still is"
|
||||||
|
"(defn g [x f65] f64 x)" ~needle:"unknown type f65 — did you mean f64?";
|
||||||
|
|
||||||
(* ── Computed global initialisers ──────────────────────────────────
|
(* ── Computed global initialisers ──────────────────────────────────
|
||||||
The order they run in is the compiler's to choose, so a global written
|
The order they run in is the compiler's to choose, so a global written
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user