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:
parent
fbbd6c4984
commit
ac7ee912e7
28
lib/check.ml
28
lib/check.ml
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user