A refusal that suggests a fix suggests one that compiles, for vec-new, map-new and a near miss that is a value

This commit is contained in:
Joseph Ferano 2026-09-25 10:30:52 +07:00
parent 6c9635cd53
commit 83b244475a
3 changed files with 60 additions and 4 deletions

View File

@ -1034,6 +1034,14 @@ should not pay for identity and metadata. Not implemented.
function nosuch" twice at the same place and counts 2 errors — once from the
abstract pass and once from the instantiation.
** DONE Two refusals suggested something that does not compile
CLOSED: [2026-09-25]
=vec-new= and =map-new= with no type no longer say "or give the binding a type";
they name the type arguments alone, and =(the T expr)= joins them when it lands.
An unknown call whose near miss is a value — =(context-allocator)= against
=context/allocator=, or a global — says the name is a value written without
parentheses, and names no call at all when the call had arguments.
* Backends
** DONE The x86 backend tracks LLVM at -O0

View File

@ -6751,7 +6751,7 @@ and vec_new_elem ctx ~want loc args =
| _ ->
fail loc
"nothing here says what (vec-new) is a Vec of — write the element \
type, as (vec-new i32), or give the binding a type")
type, as (vec-new i32)")
(* A type written as an argument to vec-new or map-new, read back out of the
expression Parse made of it. Only the shapes that cannot be a value there:
@ -6817,18 +6817,18 @@ and map_new_types ctx ~want loc args =
| a :: _ when type_of_expr a <> None ->
fail loc
"(map-new) names a key and no value — write both, as (map-new string \
i32), or give the binding a type"
i32)"
| { Ast.e = Ast.Var k; _ } :: rest when is_type k && rest = [] ->
fail loc
"(map-new %s) names a key and no value — write both, as (map-new %s \
i32), or give the binding a type" k k
i32)" k k
| _ ->
(match want with
| Some (Types.Map (k, v)) -> k, v, args
| _ ->
fail loc
"nothing here says what (map-new) maps — write the key and value \
types, as (map-new string i32), or give the binding a type")
types, as (map-new string i32)")
(* The element type, or the reason this is not a Vec. *)
and vec_elem loc what (t : Types.t) =
@ -9219,7 +9219,33 @@ and ordinary_call ctx ~want loc name args =
| Some _ as m -> m
| None -> if capitalised then near_miss ctx.env name else None
in
(* A near miss that names a value rather than a function is still the
near miss, but [(m)] would be refused in its turn, so the sentence
says how that name is written instead. *)
let callable m =
let fn_ty = function
| Types.Fn _ | Types.CFn _ | Types.Dyn -> true
| _ -> false
in
match lookup ctx m with
| Some b -> fn_ty b.bty
| None ->
match Hashtbl.find_opt ctx.env.globals m with
| Some (ty, _) -> fn_ty ty
| None ->
not (List.mem m [ "true"; "false"; "nil"; "None";
"context/allocator"; "context/temp" ])
in
match guess with
| Some m when not (callable m) ->
if args = [] then
Loc.failk "check/unknown-function" loc
"unknown function %s — did you mean %s? It is a value and not a \
function, so it is written without parentheses" name m
else
Loc.failk "check/unknown-function" loc
"unknown function %s. The nearest name, %s, is a value and not a \
function" name m
| Some m ->
Loc.failk "check/unknown-function" loc
"unknown function %s — did you mean %s?" name m

View File

@ -6375,6 +6375,28 @@ let () =
"(defmacro idm [x] x) \
(defn f [] u64 (idm 18446744073709551615))";
(* ── Suggestions that compile ─────────────────────────────────── *)
(* A let binding has no type slot, so the refusal names only the spelling
that works. *)
rejects_check "vec-new with no element type names only the type argument"
"(defn f [] i32 (let [v (vec-new)] 0))"
~needle:"as (vec-new i32)";
(match checked "(defn f [] i32 (let [m (map-new)] 0))" with
| _ -> check "map-new with no types is refused" false
| exception Loc.Error d ->
check "map-new's refusal does not suggest a binding type"
(not (contains d.Loc.dmsg "binding")));
(* The near miss is a value, and is suggested without the parentheses that
would make it a refused call. *)
rejects_check "a near miss that is a value says it is written bare"
"(defn f [] i32 (let [a (context-allocator)] 0))"
~needle:"did you mean context/allocator? It is a value and not a function";
accepts "the bare spelling that refusal names compiles"
"(defn f [] i32 (let [a context/allocator] 0))";
rejects_check "a near miss that is a function keeps the plain suggestion"
"(defn foo [] i32 1) (defn f [] i32 (fooo))"
~needle:"did you mean 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);