From cb757868b40f1cabb117246374576bb981559936 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 14:55:47 +0700 Subject: [PATCH] Keep the printed struct a colon; it is a wire format Emacs reads back render.ml's output is parsed by emacs/flan-inspect.el, which hard-codes the colon when it reads a field out of a rendered struct. Moving the printer on its own would break inspection in the dev loop without breaking a test that says so, so the printer waits and moves with its reader, in the Emacs lane. The sweep could not tell a rendered *expectation* from a Flan *source* snippet -- both are strings in a test -- so it converted both. The suite named every one it got wrong, and those are back. emacs/test-flan-dev.el:415 is the one edit inside emacs/: Flan source sent to the daemon for eval, which the parser now refuses in the old spelling. One label, in a fixture. --- emacs/test-flan-dev.el | 2 +- lib/render.ml | 6 +++++- test/test_acceptance.ml | 6 +++--- test/test_dev.ml | 2 +- test/test_flan.ml | 14 ++++++++++++++ test/test_repl.ml | 4 ++-- 6 files changed, 26 insertions(+), 8 deletions(-) diff --git a/emacs/test-flan-dev.el b/emacs/test-flan-dev.el index 7a89d58..4dbb798 100644 --- a/emacs/test-flan-dev.el +++ b/emacs/test-flan-dev.el @@ -412,7 +412,7 @@ is written instead — the real `message' call the real command makes." ;; that errors stops it on its own game thread, in a frame of its own — not ;; inside anything this client asked for. Nothing tells Emacs. (flan-dev--eval - "(defn step [] i64 (restart-case (do (error (Missing {:id 7})) 0) (use-placeholder [] -1)))" + "(defn step [] i64 (restart-case (do (error (Missing {.id 7})) 0) (use-placeholder [] -1)))" "form") (let ((deadline (+ (float-time) 20))) (while (and (not flan-dev--stopped) (< (float-time) deadline)) diff --git a/lib/render.ml b/lib/render.ml index f8f6377..1346521 100644 --- a/lib/render.ml +++ b/lib/render.ml @@ -147,7 +147,11 @@ let rec render c depth (e : Tast.expr) : Tast.expr list = (fun i (f : Tast.field) -> let v = { Tast.e = Tast.Field (e, i); ty = f.Tast.fty; loc } in (if i = 0 then [] else [ lit " " ]) - @ [ lit ("." ^ f.Tast.fname ^ " ") ] + (* Still a colon, deliberately. This printed form is a wire + format: emacs/flan-inspect.el parses it back, and it + hard-codes the colon. Moving the printer to the dot has + to land with that reader, in the Emacs lane. *) + @ [ lit (":" ^ f.Tast.fname ^ " ") ] @ render c (depth + 1) v) shown) in diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index e2156bd..04efbf4 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -150,15 +150,15 @@ let () = let println_out = "plain string\nplain bytes\n42\n-7\n5\n18446744073709551615\n3.5\n\ -0.25\ntrue\nfalse\n()\n:green\n:red\n\n(some 0)\nnone\n\ - (Blob {.id 7 .name \"sandy \\\"quoted\\\"\" .pos (V {.x 1.5 .y -2}) .tags [ 0 42 0]})\n\ + (Blob {:id 7 :name \"sandy \\\"quoted\\\"\" :pos (V {:x 1.5 :y -2}) :tags [ 0 42 0]})\n\ [ 0 0 9 0]\n[ 0 0 0 0 0 0 0 0 ...]\n[ 0 9 0]\n\ - (D1 {.d (D2 {.d (D3 {.d (D4 {.d (D5 {.n ...})})})})})\n[ 0 0]\n\ + (D1 {:d (D2 {:d (D3 {:d (D4 {:d (D5 {:n ...})})})})})\n[ 0 0]\n\ [ 0 0]\n[ 9 0]\n" (* The escape buffer is 1024 and the input is 1100 x's, so this is the truncation: the ellipsis goes *inside* the quotes, and the count is spelled out rather than pasted so that a change to the buffer or to the reserve shows up here as a number and not as a wall of x. *) - ^ "(Long {.s \"" ^ String.make 1014 'x' ^ "...\"})\n" + ^ "(Long {:s \"" ^ String.make 1014 'x' ^ "...\"})\n" ^ "abc\n" in outputs "println, every arm" "programs/println.flan" println_out; diff --git a/test/test_dev.ml b/test/test_dev.ml index 02d708e..15b3d8c 100644 --- a/test/test_dev.ml +++ b/test/test_dev.ml @@ -775,7 +775,7 @@ let () = let want = [ ("n", "i64", "3"); ("label", "string", "\"hello\""); - ("p", "Point", "(Point {.x 1.5 .y 2.5})"); + ("p", "Point", "(Point {:x 1.5 :y 2.5})"); ("xs", "[3 i32]", "[ 10 20 30]"); ("flag", "bool", "true") ] in diff --git a/test/test_flan.ml b/test/test_flan.ml index 3c0a14d..a4e501b 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -618,6 +618,12 @@ let () = rejects_check "field given twice" (cursor ^ "(defn f [s [u8]] Cursor (Cursor {.pos 0 .pos 1}))") ~needle:"given twice"; + (* The old spelling is refused rather than quietly accepted, and the refusal + names the new one. Two accepted spellings is how two spellings become + permanent, and the colon is wanted for keys. *) + rejects_check "a field label written with a colon" + (cursor ^ "(defn f [s [u8]] Cursor (Cursor {:src s}))") + ~needle:"a field label is written .src, not :src"; accepts "field through a pointer auto-derefs" (cursor ^ "(defn f [c (Ptr Cursor)] i32 (.pos c))"); accepts "set through a pointer" @@ -957,6 +963,14 @@ let () = rejects_check "a pattern with no field name after it" (pt ^ "(defn f [p Point] i32 (let [{a b} p] 0))") ~needle:"expected .field after a"; + (* The same refusal on the destructuring side: a field is a field wherever it + is named, so the rule is not half-applied. :keys is the one that keeps its + colon, and the test below it says so. *) + rejects_check "a destructured field written with a colon" + (pt ^ "(defn f [p Point] i32 (let [{a :x} p] a))") + ~needle:"a field label is written .x, not :x"; + accepts ":keys keeps its colon, naming no field" + (pt ^ "(defn f [p Point] i32 (let [{:keys [x y]} p] (+ x y)))"); (* Clojure's other map-destructuring keys. Each is refused by its own name: "unexpected form" would leave the author guessing which of the four they diff --git a/test/test_repl.ml b/test/test_repl.ml index f642277..b8c889f 100644 --- a/test/test_repl.ml +++ b/test/test_repl.ml @@ -96,9 +96,9 @@ let () = this would otherwise come back as -1. *) value "u64 at its maximum" "big" "18446744073709551615"; (* A struct, nested, with a fixed array inside it. *) - value "a struct" "(.pos b)" "(V {.x 1.5 .y 0})"; + value "a struct" "(.pos b)" "(V {:x 1.5 :y 0})"; value "a nested struct" "b" - "(Blob {.id 7 .name \"sandy \\\"quoted\\\"\" .pos (V {.x 1.5 .y 0}) .tags [ 0 42 0]})"; + "(Blob {:id 7 :name \"sandy \\\"quoted\\\"\" :pos (V {:x 1.5 :y 0}) :tags [ 0 42 0]})"; value "a fixed array" "arr" "[ 0 0 9 0]"; (* A slice's length is not known until it runs, so this one renders through a loop rather than by unrolling. *)