diff --git a/emacs/flan-inspect.el b/emacs/flan-inspect.el index 7ad77f32..e009669e 100644 --- a/emacs/flan-inspect.el +++ b/emacs/flan-inspect.el @@ -133,7 +133,7 @@ reply without a daemon behind them, and so that this file names ;; its span bound of 8 fields ;; (Name A {.f V}) an instance of a generic struct, its type arguments ;; kept on the head (:text) and the name alone as :type -;; [ V V V] an array or a slice, ` ...' likewise +;; [V V V] an array or a slice, ` ...' likewise ;; (some V) / none an option ;; a pointer, never followed ;; a named type the walk had no structure for diff --git a/emacs/test-flan-cider.el b/emacs/test-flan-cider.el index f3b10da3..fcb43080 100644 --- a/emacs/test-flan-cider.el +++ b/emacs/test-flan-cider.el @@ -76,7 +76,7 @@ ;; A generic struct's instance, its type arguments between the name and the ;; brace, compound ones included. Written back as it was read. -(let* ((src "(Pair (Option u8) [3 i32] {.a (some 1) .b [ 1 2 3]})") +(let* ((src "(Pair (Option u8) [3 i32] {.a (some 1) .b [1 2 3]})") (n (flan-inspect-parse src))) (test-flan--check "a generic instance is a struct" (eq (plist-get n :kind) 'struct)) @@ -92,13 +92,13 @@ :children)) '("a"))) -;; [ 0 42 0] — Types.Slice and Types.Array both write this. +;; [0 42 0] — Types.Slice and Types.Array both write this. (let ((n (flan-inspect-parse "[0 42 0]"))) (test-flan--check "a sequence is a sequence" (eq (plist-get n :kind) 'seq)) (test-flan--check "indexed from zero" (equal (mapcar #'car (plist-get n :children)) '(0 1 2)))) -;; [ [ 0 0] [ 1 ...] ...] — span truncation at both levels, which is what +;; [[0 0] [1 ...] ...] — span truncation at both levels, which is what ;; sand's [100 [100 u32]] actually produces. (let* ((n (flan-inspect-parse "[[0 0] [1 ...] ...]")) (kids (plist-get n :children))) diff --git a/lib/check.ml b/lib/check.ml index f3f2702b..f6058420 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -3454,7 +3454,13 @@ let view_elem_lit loc (k : int64) = (* A fix is spelled in the syntax of the file the mistake is in: the checker sees one AST for both, so the location's file is the only thing left that says which one the reader is looking at. *) -let fln_source (loc : Loc.t) = Filename.check_suffix loc.Loc.file ".fln" +let fln_source (loc : Loc.t) = Source.indented_at loc + +(* Which form defined each mutable global, [defonce] or [def], so a fix that + rewrites the definition keeps the form the programmer chose. Filled where + globals are collected; a name missing from it (a defconst) is given + [defonce]. *) +let global_forms : (string, Ast.reinit) Hashtbl.t = Hashtbl.create 16 (* 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. *) @@ -3496,6 +3502,18 @@ let view_refusal kind loc (e : Tast.expr) reason = ( 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 when (match e.Tast.e with Tast.Global _ -> true | _ -> false) -> + let every = + match e.Tast.e with + | Tast.Global g -> Hashtbl.find_opt global_forms g = Some Ast.Every + | _ -> false + in + ( Printf.sprintf "%s is a %s" n ty, + Printf.sprintf "Define %s as a dyn value, as in %s" n + (if fln then + Printf.sprintf "%s %s: dyn = [...]" (if every then "def" else "once") n + else + Printf.sprintf "(%s %s dyn [...])" (if every then "def" else "defonce") n) ) | 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 @@ -14601,6 +14619,7 @@ let collect env (decls : Ast.decl list) = (match k with Ast.Once -> "defonce" | Ast.Every -> "def") n in Hashtbl.replace env.globals n (ty, false); + Hashtbl.replace global_forms n k; Hashtbl.replace env.global_locs n loc | Ast.Defconst (n, Some t, _) -> Hashtbl.replace env.globals n (resolve env t, true); diff --git a/test/test_syntax.ml b/test/test_syntax.ml index f71e6001..1da1c3c6 100644 --- a/test/test_syntax.ml +++ b/test/test_syntax.ml @@ -610,6 +610,28 @@ let () = 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"; + (* A global's fix redefines it, in the form it was defined with. *) + let show_flan = "(defn show [d dyn] i32 1)\n" in + refused "view-global.flan" + ("(defonce gs [2 i32] [1 2])\n" ^ show_flan ^ "(defn main [] i32 (show gs))\n") + [ "gs is a [2 i32]"; "as in (defonce gs dyn [...])" ]; + refused "view-global-def.flan" + ("(def gs [2 i32] [1 2])\n" ^ show_flan ^ "(defn main [] i32 (show gs))\n") + [ "as in (def gs dyn [...])" ]; + refused "view-global.fln" + "once gs: [2 i32] = [1 2]\n\nfn show(d) -> i32 = 1\n\nfn main() -> i32 = show(gs)\n" + [ "gs is a [2 i32]"; "as in once gs: dyn = [...]" ]; + checks "view-global-fix.flan" + ("(defonce gs dyn [1 2])\n(def hs dyn [1 2])\n" ^ show_flan + ^ "(defn main [] i32 (show gs) (show hs))\n"); + checks "view-global-fix.fln" + "once gs: dyn = [1 2]\n\nfn show(d) -> i32 = 1\n\nfn main() -> i32 = show(gs)\n"; + (* The fix is spelled in the syntax the code was sent in, not the one the + file's name implies: an editor request from an indented buffer. *) + Source.with_code ~syntax:Source.Indented ~at:None (fun () -> + refused "unit-tail-request.flan" + "(defn f [coll] dyn (let [i 1] (while (< i 3) (++ i))))\n(defn main [] () (f 1))\n" + [ "fn f(...) -> ()" ]); (* 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");