A wrong main signature points at the main that is wrong

check_main raised against Loc.unknown, so both of its refusals opened with
<unknown>:0:0. env.locs is the table of where each type was declared and a
function is not in it, so the location comes from the declaration list the
caller already holds. A main that arrived without a defn keeps the unknown
span rather than being given an invented one.
This commit is contained in:
Joseph Ferano 2026-09-17 21:44:40 +07:00
parent fbbd6c4984
commit ac7ee912e7
2 changed files with 45 additions and 4 deletions

View File

@ -6272,7 +6272,27 @@ let check_global env (d : Ast.decl) : Tast.global option =
(* The entry point, plan.org: (defn main [args [string]] i32), with both the
parameter and the return type optional. *)
let check_main env =
(* Where [main] was written. [env.locs] is the table of where each *type* was
declared [collect] fills it for structs, data types, unions and enums and
for nothing else so a function's own location is not in it and the
[find_opt] idiom the rest of this file uses does not apply here. The
declaration list does have it, and the caller is holding the list anyway.
Without this both refusals below opened with <unknown>:0:0, which tells a
reader that a rule exists and not where they broke it, and gives
[next-error] nothing to jump to. A [main] that arrived some other way a
[declare], say still has no [defn] to point at, so that case keeps the
unknown span rather than inventing one. *)
let main_loc (decls : Ast.decl list) =
let is_main (d : Ast.decl) =
match d.Ast.d with Ast.Defn fn -> fn.Ast.name = "main" | _ -> false
in
match List.find_opt is_main decls with
| Some { Ast.d = Ast.Defn fn; _ } -> fn.Ast.nloc
| _ -> Loc.unknown
let check_main env decls =
let at = main_loc decls in
match Hashtbl.find_opt env.fns "main" with
| None -> () (* a library, or a file being checked on its own *)
| Some (params, ret) ->
@ -6283,12 +6303,12 @@ let check_main env =
| _ -> false
in
if not ok_params then
fail Loc.unknown
fail at
"main takes no parameters or one [string], not (%s)"
(String.concat " " (List.map Types.to_string params));
if not (Types.equal ret Types.Unit || Types.equal ret (Types.Int Types.I32))
then
fail Loc.unknown "main returns i32 or nothing, not %s"
fail at "main returns i32 or nothing, not %s"
(Types.to_string ret)
(* The environment as well as the program. A session needs it to check an
@ -6321,7 +6341,7 @@ let build_program ~keep_going (decls : Ast.decl list) : Tast.program * env =
check_finite env;
check_union_members env;
let s = Loc.sink ~on:keep_going in
ignore (Loc.caught s (fun () -> check_main env));
ignore (Loc.caught s (fun () -> check_main env decls));
(* Every generic body, checked once with its variables left abstract, and
the result thrown away. This is the pass plan.org's rule needs and Odin
has no equivalent of: Odin checks a polymorphic body only per

View File

@ -945,6 +945,27 @@ let () =
~needle:"main takes no parameters";
rejects_check "main returning the wrong type" "(defn main [] bool true)"
~needle:"main returns i32";
(* And both of them point at the [main] that is wrong. They used to open with
<unknown>:0:0 the checker's rule about the entry point is the one place
that had a name and no span, because [env.locs] records where a *type* was
declared and a function is not in it. The span is the whole difference
between a message you can act on and a message you have to go looking for,
so it is pinned here rather than left to the reader of a report. *)
let main_at name src =
match checked src with
| _ ->
incr failures;
Printf.printf "FAIL %s: expected a type error\n" name
| exception Loc.Error { Loc.dloc; _ } ->
let got = Loc.to_string dloc in
if got <> "<test>:1:7" then begin
incr failures;
Printf.printf "FAIL %s\n wanted: %s\n got: %s\n"
name "<test>:1:7" got
end
in
main_at "a wrong main parameter points at main" "(defn main [n i32] ())";
main_at "a wrong main return type points at main" "(defn main [] bool true)";
(* ── Unconstrained operators, and everything past milestone 2 ──── *)
rejects_check "no built-in = on strings"