diff --git a/lib/check.ml b/lib/check.ml index 76fe205c..a335ea94 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -3456,6 +3456,10 @@ let view_elem_lit loc (k : int64) = says which one the reader is looking at. *) let fln_source (loc : Loc.t) = Filename.check_suffix loc.Loc.file ".fln" +(* The function being checked and its parameters, by slot. A stack because + a generic's copy is checked from inside the body that called it. *) +let grow_params : (ctx * (int * Ast.field) list) list ref = ref [] + (* The two refusals below share their subject and their fix. The subject is the name as written when the refused value is a bare name, so the message can say [a is a [4 i64]]; anything longer is "this". The fix is the one @@ -3467,7 +3471,7 @@ let view_subject (e : Tast.expr) = | Some s when s <> "" && String.for_all - (fun c -> not (List.mem c [ ' '; '('; ')'; '['; ']'; '{'; '}'; '"' ])) + (fun c -> not (List.mem c [ ' '; '('; ')'; '['; ']'; '{'; '}'; '"'; '.'; ',' ])) s -> Some s | _ -> None @@ -3475,14 +3479,29 @@ let view_subject (e : Tast.expr) = let view_refusal kind loc (e : Tast.expr) reason = let ty = Types.to_string e.Tast.ty in let fln = fln_source loc in + (* A parameter is made by the caller, so its fix is its declaration. The + name is compared as well as the slot: a closure numbers its slots from + zero too, and is checked while its enclosing function is on the stack. *) + let param = + match e.Tast.e, view_subject e, !grow_params with + | Tast.Local s, Some n, (ctx, ps) :: _ -> + (match List.assoc_opt s ps with + | Some (p : Ast.field) when p.Ast.fname = n -> Some (ctx.owner, n) + | _ -> None) + | _ -> None + in let subject, fix = - match view_subject e with - | Some n -> + match param, view_subject e with + | Some (f, n), _ -> + ( Printf.sprintf "%s is a %s parameter" n ty, + Printf.sprintf "Declare %s as dyn in %s's parameters: %s%s" n f n + (if fln then ": dyn" else " dyn") ) + | None, Some n -> ( Printf.sprintf "%s is a %s" n ty, Printf.sprintf "Build %s as a dyn value where it is made, as in %s" n (if fln then Printf.sprintf "let %s: dyn = [...]" n else Printf.sprintf "(let [%s (the dyn [...])] ...)" n) ) - | None -> + | None, None -> ( Printf.sprintf "This is a %s" ty, Printf.sprintf "Build it as a dyn value where it is made, as in %s" (if fln then "the(dyn, [...])" else "(the dyn [...])") ) @@ -5153,11 +5172,9 @@ let if_depth = ref 0 (* A Vec or a Map parameter is a copy of the caller's header — Odin's rule — so growing it reallocates a block only this function's copy points at, and - the caller's container never sees the elements. The function being checked - and its container parameters, by slot, and the warnings found so far, one - per parameter, printed by [build_program]. A stack because a generic's copy - is checked from inside the body that called it. *) -let grow_params : (ctx * (int * Ast.field) list) list ref = ref [] + the caller's container never sees the elements. The warnings found so far, + one per parameter, printed by [build_program]; the parameters themselves + are [grow_params], above [view_refusal], which reads them too. *) let grow_warnings : Loc.diag list ref = ref [] let note_grown ctx op loc (target : Tast.expr) = diff --git a/test/test_syntax.ml b/test/test_syntax.ml index b30fb11d..72a537ec 100644 --- a/test/test_syntax.ml +++ b/test/test_syntax.ml @@ -589,6 +589,18 @@ let () = (poke_fln ^ "fn main() -> ()\n let d = [6 2 4 9]\n poke(d)\n") [ "d is a [4 i32]"; "only when its elements are i64, f64 or bool, and these are i32"; "as in let d: dyn = [...]" ]; + (* A parameter is made by the caller, so its fix is its declaration. *) + refused "view-param.fln" + "fn take(d) -> i32 = 1\n\nfn give(n: i32, v: [4 i64]) -> i32\n take(v)\n\n\ + fn main() -> i32 = 0\n" + [ "v is a [4 i64] parameter"; "Declare v as dyn in give's parameters: v: dyn" ]; + refused "view-param.flan" + "(defn take [d dyn] i32 1)\n(defn give [n i32 v (Vec i64)] i32 (take v))\n\ + (defn main [] i32 0)\n" + [ "v is a (Vec i64) parameter"; "Declare v as dyn in give's parameters: v dyn" ]; + checks "view-param-fix.fln" + "fn take(d) -> i32 = 1\n\nfn give(n: i32, v: dyn) -> i32\n take(v)\n\n\ + fn main() -> i32 = 0\n"; (* The fix both of them name. *) checks "view-fix.fln" (poke_fln ^ "fn main() -> ()\n let d: dyn = [6 2 4 9]\n poke(d)\n poke(the(dyn, [1 2]))\n");