From 83b244475a9a0af788715f76926a744e3d1b8e08 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Fri, 25 Sep 2026 10:30:52 +0700 Subject: [PATCH] A refusal that suggests a fix suggests one that compiles, for vec-new, map-new and a near miss that is a value --- TODO.org | 8 ++++++++ lib/check.ml | 34 ++++++++++++++++++++++++++++++---- test/test_flan.ml | 22 ++++++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/TODO.org b/TODO.org index c94d08a4..2c65948b 100644 --- a/TODO.org +++ b/TODO.org @@ -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 diff --git a/lib/check.ml b/lib/check.ml index 40a65e3c..34c05c4d 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -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 diff --git a/test/test_flan.ml b/test/test_flan.ml index 758848c1..b24cb9f6 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -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);