The trio the author decided on 2026-09-20 is now all built: def is CL's defparameter — its initialiser runs on every daemon re-run, unguarded, so an edited initialiser repaints the same storage on C-c C-c plus re-run — defonce (Clojure's name for CL's defvar, per the author) initialises once behind the .init~once. flag, and defconst stays the image. One parse arm reads both forms; the difference is Ast.reinit, carried to Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and Check.check_global lifts every def initialiser — zero and literal included — into global/<n>, so the host's startup reaches it through the function cell and a re-evaluated def swaps it (Session's def_inits; Emit.redefinition declares the cell for a non-sibling target). The old defvar spelling is refused with the rename and both compiling spellings, and every program, test, doc and editor list is swept — except sand.flan, the author's live WIP, whose seven defvar lines are flagged in FIX.org and keep its three dependent tests red on this branch.
62 lines
2.2 KiB
Plaintext
62 lines
2.2 KiB
Plaintext
;;;; The cleanup paths nothing was watching.
|
|
;;;;
|
|
;;;; Found by mutation testing: each of the six claims below could be broken in
|
|
;;;; lib/check.ml, lib/emit.ml or runtime/flan_rt.c and the whole suite stayed
|
|
;;;; green. Every case here is one a plausible wrong version gets wrong, and
|
|
;;;; the numbers differ per failure so a single wrong answer names its cause.
|
|
(defstruct Missing [id i32])
|
|
(defstruct Other [id i32])
|
|
|
|
(defonce log i64)
|
|
(defonce order i64)
|
|
(defonce seen i64)
|
|
|
|
(defn note [n i64] () (set order (+ (* order 10) n)))
|
|
|
|
;;; (1) An early return must run the defers registered above it, and (2) it
|
|
;;; must run them innermost-first. A defer that (8) *calls* something is the
|
|
;;; case that puts a guard inside the defer, on the transfer path.
|
|
(defn early [] i64
|
|
(defer (note 1))
|
|
(defer (note 2))
|
|
(return 7)
|
|
0)
|
|
|
|
;;; (5) A transfer out of a handler-bind has to pop its frames on the way past,
|
|
;;; and (6) a handler-bind with two clauses has to pop both, innermost-first.
|
|
;;; If either leaks, the stack keeps a frame pointing into a function that has
|
|
;;; gone, and the next signal calls into it.
|
|
(defn deep [] i32 (signal (Missing {.id 1})) 0)
|
|
|
|
(defn leaky [] i32
|
|
(restart-case
|
|
(handler-bind [(Other [c] (set seen (+ seen 1000)))
|
|
(Missing [c] (invoke-restart 'skip))]
|
|
(deep))
|
|
(skip [] 42)))
|
|
|
|
;;; (7) Once a handler has answered a signal by transferring, the walk stops:
|
|
;;; an outer handler of the same type must not also run.
|
|
(defn nested [] i32
|
|
(restart-case
|
|
(handler-bind [(Missing [c] (set seen (+ seen 100)))]
|
|
(handler-bind [(Missing [c] (invoke-restart 'stop))]
|
|
(deep)))
|
|
(stop [] 5)))
|
|
|
|
(defn main [] i32
|
|
(print (early)) (println "") ; 7
|
|
(print order) (println "") ; 21 — innermost first, both ran
|
|
|
|
(print (leaky)) (println "") ; 42
|
|
;; The handler stack must be empty again. If a frame leaked, this signal
|
|
;; reaches it and seen moves.
|
|
(deep)
|
|
(print seen) (println "") ; 0
|
|
|
|
(set seen 0)
|
|
(print (nested)) (println "") ; 5
|
|
(print seen) (println "") ; 0 — the outer handler did not run
|
|
(print log) (println "") ; 0
|
|
0)
|