A defn with no return type says so, and an operator spelled as another language spells it names Flan's
This commit is contained in:
commit
9bb1bde665
103
lib/check.ml
103
lib/check.ml
@ -407,6 +407,37 @@ let spell_arg stand_for (a : Ast.expr) =
|
||||
| Ast.UInt (_, s) -> s
|
||||
| _ -> stand_for
|
||||
|
||||
(* Operators other languages spell differently, each mapped to the Flan
|
||||
builtin that computes the same thing. Only exact equivalents: [mod] is left
|
||||
out because Clojure's is floored and [%] is not. *)
|
||||
let operator_aliases =
|
||||
[ ("not=", ("!=", "Not-equal")); ("=/=", ("!=", "Not-equal"));
|
||||
("/=", ("!=", "Not-equal")); ("<>", ("!=", "Not-equal"));
|
||||
("==", ("=", "Equality")); ("===", ("=", "Equality"));
|
||||
("&&", ("and", "Logical and")); ("||", ("or", "Logical or"));
|
||||
("!", ("not", "Logical not")) ]
|
||||
|
||||
(* The fix, as the sentence that ends the refusal. The reader's call is
|
||||
written back out under the Flan name only when every argument can be
|
||||
spelled and the count is one the builtin takes, so a suggestion printed as
|
||||
code compiles once pasted. An argument that cannot be spelled leaves the
|
||||
call as it was, with only the name to change; a count the builtin does not
|
||||
take gets the builtin's shape. *)
|
||||
let alias_fix flan (args : Ast.expr list) =
|
||||
let spelled = List.map (spell_arg "") args in
|
||||
let n = List.length args in
|
||||
let arity_ok =
|
||||
match flan with
|
||||
| "not" -> n = 1
|
||||
| "and" | "or" -> true
|
||||
| _ -> n >= 2
|
||||
in
|
||||
if not arity_ok then
|
||||
Printf.sprintf "It is called as %s"
|
||||
(if flan = "not" then "(not x)" else "(" ^ flan ^ " x y)")
|
||||
else if List.mem "" spelled then Printf.sprintf "Write %s in its place" flan
|
||||
else Printf.sprintf "Write (%s)" (String.concat " " (flan :: spelled))
|
||||
|
||||
(* What a [break] or a [continue] may be talking about, innermost first.
|
||||
|
||||
[Lloop] is a loop it is lexically inside, carrying its label if it was given
|
||||
@ -1212,6 +1243,21 @@ let rec resolve env ?(seen = []) (t : Ast.texpr) : Types.t =
|
||||
[Types.t] and therefore to the layout calculator, both backends,
|
||||
[Render] and the DWARF path. docs/SPIKE-GENERICS.md, question 4,
|
||||
prices it and leaves it out. *)
|
||||
(* A head that is not a type at all but one edit from one is the typo
|
||||
[(Vect i32)], and the generics sentence would answer a question
|
||||
nobody asked. *)
|
||||
let constructors = [ "Ptr"; "Option"; "Vec"; "Map" ] in
|
||||
(match
|
||||
if Hashtbl.mem env.aliases name || Hashtbl.mem env.structs name
|
||||
|| Hashtbl.mem env.datas name || Hashtbl.mem env.unions name
|
||||
|| Hashtbl.mem env.enums name
|
||||
then None
|
||||
else near_miss env ~also:constructors name
|
||||
with
|
||||
| Some m when List.mem m constructors ->
|
||||
Loc.failk "check/unknown-type" loc
|
||||
"unknown type %s — did you mean %s?" name m
|
||||
| _ -> ());
|
||||
fail loc
|
||||
"%s takes no type arguments. A generic function is written with $t \
|
||||
in its parameter vector; a generic type is not there yet"
|
||||
@ -1411,6 +1457,53 @@ let is_type_name env n =
|
||||
slot holding one is a type however few of them there are. *)
|
||||
|| (n <> "" && n.[0] = '$')
|
||||
|
||||
(* A defn written without its return type puts the body's first form in the
|
||||
slot, and [(dotimes [i n] ...)] parses as a type application. Every type
|
||||
application's head is a constructor, and a constructor is capitalised, so a
|
||||
lowercase head there — or the name of a function — is a body form and not a
|
||||
malformed type. A capitalised head that is neither stays with [resolve],
|
||||
whose unknown-type and no-type-arguments sentences are the right ones for
|
||||
[(Vect i32)] and [(Pair i32)]. *)
|
||||
let missing_return_type env (fn : Ast.fn) =
|
||||
match fn.Ast.ret with
|
||||
| Some { Ast.t = Ast.Tapp (head, args); tloc } ->
|
||||
let lowercase =
|
||||
head <> "" && not (head.[0] >= 'A' && head.[0] <= 'Z')
|
||||
in
|
||||
let constructor =
|
||||
List.mem head [ "Ptr"; "Option"; "Vec"; "Map"; "Result" ]
|
||||
in
|
||||
(* [(vec i32)] is the constructor with the wrong case, not a body form —
|
||||
but only while every argument is a type, since [(map inc xs)] is a
|
||||
body form whose head is a function. *)
|
||||
let args_are_types =
|
||||
List.for_all
|
||||
(fun (a : Ast.texpr) ->
|
||||
match a.Ast.t with Ast.Tname n -> is_type_name env n | _ -> true)
|
||||
args
|
||||
in
|
||||
(match
|
||||
List.find_opt
|
||||
(fun c -> String.lowercase_ascii c = String.lowercase_ascii head
|
||||
&& c <> head)
|
||||
[ "Ptr"; "Option"; "Vec"; "Map" ]
|
||||
with
|
||||
| Some c when args_are_types && not (is_type_name env head) ->
|
||||
Loc.failk "check/unknown-type" tloc
|
||||
"unknown type %s — did you mean %s?" head c
|
||||
| _ -> ());
|
||||
if (not constructor) && (not (is_type_name env head))
|
||||
&& (lowercase || Hashtbl.mem env.fns head
|
||||
|| List.mem head !builtin_names)
|
||||
then
|
||||
Loc.failk "check/return-type-missing" tloc
|
||||
"%s has no return type: (%s ...) stands where the return type goes, \
|
||||
and %s is not a type. The return type is written between the \
|
||||
parameter vector and the body, and a function that returns nothing \
|
||||
writes () there"
|
||||
fn.Ast.name head head
|
||||
| _ -> ()
|
||||
|
||||
(* Before a bare symbol is allowed to become an unannotated parameter, the two
|
||||
ways it is more likely to be a type that went wrong.
|
||||
|
||||
@ -9370,6 +9463,15 @@ and ordinary_call ctx ~want loc name args =
|
||||
name dname dname c.Tast.vname dname c.Tast.vname
|
||||
else if Hashtbl.mem ctx.env.structs name then
|
||||
positional_struct ctx ~want loc name args
|
||||
else if List.mem_assoc name operator_aliases then
|
||||
(* Asked before the package test, because [/=] and [=/=] have a slash
|
||||
in them and are not package calls. The did-you-mean cannot reach
|
||||
these: [not=] is one edit from [not], which is the wrong answer,
|
||||
and [&&] is no edit at all from [and]. *)
|
||||
let flan, what = List.assoc name operator_aliases in
|
||||
Loc.failk "check/unknown-function" loc
|
||||
"there is no %s. %s is %s. %s" name what flan
|
||||
(alias_fix flan args)
|
||||
else if String.contains name '/' then
|
||||
unimplemented loc
|
||||
(Printf.sprintf "the call %s into an imported package" name) 4
|
||||
@ -10995,6 +11097,7 @@ let collect env (decls : Ast.decl list) =
|
||||
let fields = List.map field ms in
|
||||
Hashtbl.replace env.unions n { Tast.sname = n; fields }
|
||||
| Ast.Defn fn ->
|
||||
missing_return_type env fn;
|
||||
(* A signature that introduces a type variable is a *pattern*, not a
|
||||
signature: it goes in [gsigs] and the function goes nowhere near
|
||||
[fns], because nothing can be called at [t]. Every call site turns
|
||||
|
||||
@ -2446,6 +2446,69 @@ let () =
|
||||
reach past anything. Four claims: the call is refused, the refusal says
|
||||
what to write instead, the name binds in every position, and a defn under
|
||||
it earns no shadowing warning. *)
|
||||
(* ── Operators spelled as other languages spell them ─────────────── *)
|
||||
rejects_check "not= names !="
|
||||
"(defn f [a i32 b i32] bool (not= a b))"
|
||||
~needle:"there is no not=. Not-equal is !=. Write (!= a b)";
|
||||
accepts "and the call it writes compiles"
|
||||
"(defn f [a i32 b i32] bool (!= a b))";
|
||||
rejects_check "not= over calls says which name to change"
|
||||
"(defn f [a i32 b i32] bool (not= (+ a 1) b))"
|
||||
~needle:"Write != in its place";
|
||||
rejects_check "/= is not a package call"
|
||||
"(defn f [a i32 b i32] bool (/= a b))" ~needle:"Write (!= a b)";
|
||||
rejects_check "=/= is not a package call"
|
||||
"(defn f [a i32 b i32] bool (=/= a b))" ~needle:"Write (!= a b)";
|
||||
rejects_check "== names ="
|
||||
"(defn f [a i32 b i32] bool (== a b))" ~needle:"Write (= a b)";
|
||||
rejects_check "&& names and"
|
||||
"(defn f [a bool b bool] bool (&& a b))" ~needle:"Write (and a b)";
|
||||
accepts "and that call compiles" "(defn f [a bool b bool] bool (and a b))";
|
||||
rejects_check "|| names or"
|
||||
"(defn f [a bool b bool] bool (|| a b))" ~needle:"Write (or a b)";
|
||||
rejects_check "! names not"
|
||||
"(defn f [a bool] bool (! a))" ~needle:"Write (not a)";
|
||||
rejects_check "a ! at an arity not does not take gets not's shape"
|
||||
"(defn f [a bool b bool] bool (! a b))" ~needle:"called as (not x)";
|
||||
rejects_check "a bare && is written back as the and that compiles"
|
||||
"(defn f [] bool (&&))" ~needle:"Write (and)";
|
||||
accepts "and it does" "(defn f [] bool (and))";
|
||||
accepts "a program's own not= is its own"
|
||||
"(defn not= [a i32 b i32] bool (!= a b)) \
|
||||
(defn f [a i32 b i32] bool (not= a b))";
|
||||
|
||||
(* ── A defn with no return type ───────────────────────────────────
|
||||
The body's first form lands in the return slot and parses as a type
|
||||
application; the refusal names the missing return type rather than the
|
||||
form's head. *)
|
||||
rejects_check "a body form in the return slot is a missing return type"
|
||||
"(defn f [s string] (dotimes [i (length s)] (println i)))"
|
||||
~needle:"f has no return type: (dotimes ...) stands where the return type goes";
|
||||
rejects_check "and says how a function returning nothing is written"
|
||||
"(defn f [a i32 b i32] (+ a b))"
|
||||
~needle:"a function that returns nothing writes () there";
|
||||
accepts "the fix compiles"
|
||||
"(defn f [s string] () (dotimes [i (length s)] (println i)))";
|
||||
accepts "a type application in the slot is still a type"
|
||||
"(defn f [] (Option i32) None)";
|
||||
rejects_check "a misspelled constructor is a near miss"
|
||||
"(defn f [] (Optoin i32) None)" ~needle:"unknown type Optoin — did you mean Option?";
|
||||
rejects_check "a lowercase vec is Vec"
|
||||
"(defn f [] (vec i32) (vec-new i32))" ~needle:"unknown type vec — did you mean Vec?";
|
||||
rejects_check "a lowercase ptr is Ptr"
|
||||
"(defn f [p (Ptr i32)] (ptr i32) p)" ~needle:"unknown type ptr — did you mean Ptr?";
|
||||
rejects_check "a lowercase option is Option"
|
||||
"(defn f [] (option i32) None)" ~needle:"unknown type option — did you mean Option?";
|
||||
rejects_check "a lowercase map is Map"
|
||||
"(defn f [] (map i32 i32) (map-new i32 i32))"
|
||||
~needle:"unknown type map — did you mean Map?";
|
||||
rejects_check "a map over values in the slot is a body form"
|
||||
"(defn f [inc i32 xs i32] (map inc xs))" ~needle:"f has no return type";
|
||||
rejects_check "an unknown capitalised head keeps the generics sentence"
|
||||
"(defn f [] (Pair i32) 0)" ~needle:"Pair takes no type arguments";
|
||||
rejects_check "a misspelled plain return type is still an unknown type"
|
||||
"(defn f [] i3 0)" ~needle:"unknown type i3";
|
||||
|
||||
rejects_check "len is not a builtin"
|
||||
"(defn f [s [i32]] i32 (len s))" ~needle:"there is no len";
|
||||
rejects_check "and the refusal writes the call out"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user