From 865c7e04459f1e9573faf4b2b5d281bdb7f7ef9d Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sun, 20 Sep 2026 18:23:33 +0700 Subject: [PATCH] C-c C-c on a defclass went to the expression evaluator and was refused flan--declaration-heads was topped up a name at a time, so it had lost defdata and all four of the object and dispatch heads. A head missing from it is not a quiet fallback: the form goes to the expression evaluator, and the daemon answers "defclass is a top-level declaration, not an expression" at the line you pressed the key on. Confirmed both ways against a real daemon on test/programs/dev-class.flan -- the old list gets that refusal, the new one installs, and defgeneric and defmethod install through the same path (area@point, area). Re-derived whole from Parse.decl, which is the honest authority. The comment said the authority was the declaration arm of Parse.expr, and that was never quite true: declare and declare-c have been in this list from the start and Parse.expr has no arm for either. So the rule is the plain one -- is it a declaration -- and package is a single named exception, kept because the two keys have always differed there and nothing about a line written once at the top of a file asks for that to change. The rationale in test-flan.el's package check said the same stale thing and says the surviving reason now. The new check reads the list off Parse.decl and asserts both keys on every head, so the derivation is the test rather than a claim beside it. --- emacs/flan.el | 58 +++++++++++++++++++++++++++------------------- emacs/test-flan.el | 40 +++++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/emacs/flan.el b/emacs/flan.el index 441d261..6e16b11 100644 --- a/emacs/flan.el +++ b/emacs/flan.el @@ -2136,34 +2136,44 @@ daemon reads that as stopping on entry instead." ;; question of it: the top-level form point is *in*, which for a bare ;; `(+ 1 1)' written at column 1 is that expression and nothing else. ;; -;; Hardcoded, which is the thing to be careful about: the authority is the -;; declaration arm of `Parse.expr' in lib/parse.ml (the "is a top-level -;; declaration, not an expression" refusal, around line 458), plus the -;; `declare'/`declare-c' arm a few forms below it. This list is that set and -;; has to be changed with it. `defunion' is being renamed `defdata', with -;; `defunion' becoming a C-style untagged union; both spellings are top-level -;; declarations either way, so this list wants `defdata' adding when that -;; lands rather than a swap. +;; Hardcoded, which is the thing to be careful about: the authority is +;; `Parse.decl' in lib/parse.ml — every head it has an arm for is a +;; declaration and belongs here — and this list is that set minus `package', +;; for the reason below. Re-derived whole when it is touched, rather than +;; topped up a name at a time: patching in the one name somebody noticed is +;; exactly how it came to be missing `defdata' and all four of the object and +;; dispatch heads for weeks after they landed. (`Parse.decl's top-level +;; `(do ...)' arm is not a head — it splices the declarations inside it, and +;; the forms this list is matched against are what comes out of that.) ;; -;; `package' is deliberately not here even though flan-mode.el's -;; `flan--definers' has it. This is the set of heads that *fail* when sent to -;; the expression evaluator, which is the bug `C-x C-e' was fixed for, and a -;; `package' form does not fail — `Parse.expr' has no arm refusing it. It is -;; still a declaration, so `C-c C-c' has to keep installing it; see -;; `flan--defun-heads' below, which is this list plus that one head. +;; It used to be derived from somewhere else: the declaration arm of +;; `Parse.expr', the "is a top-level declaration, not an expression" refusal, +;; on the reading that this is the set of heads that *fail* when sent to the +;; expression evaluator. That reading does not survive `declare' and +;; `declare-c', which have been in this list from the start and which +;; `Parse.expr' has no arm for at all: sent as expressions they parse as calls +;; of an unknown name, and the message that comes back is worse than the one +;; the declaration path gives. Which is an argument for installing them, not +;; for leaving them out. So the rule is the plain one — is it a declaration — +;; and `package' is a single named exception rather than the edge of a subtler +;; rule that was never quite true. (defconst flan--declaration-heads - '("defmacro" "defn" "defvar" "defconst" "defstruct" "defunion" "defenum" - "defalias" "import" "declare" "declare-c") + '("defmacro" "defn" "defvar" "defconst" + "defstruct" "defdata" "defunion" "defenum" "defalias" + ;; The object and dispatch heads (lib/parse.ml:1277-1334), which are + ;; declarations in exactly the way `defn' is: each introduces a top-level + ;; name and none of them is an expression. + "defclass" "defgeneric" "defmulti" "defmethod" + "import" "declare" "declare-c") "Heads whose form is a declaration, and never an expression.") -;; The two keys ask *nearly* the same question, and `package' is where the two -;; readings come apart. `C-x C-e' asks "would this fail if I sent it to the -;; expression evaluator?", and a `package' form would not: `Parse.expr' has no -;; arm refusing it, so it falls through to a call of an unknown name and the -;; message would be a worse one than the declaration path gives. `C-c C-c' -;; asks the plain question — "is this a declaration?" — and `package' is one: -;; `Parse.decl' has an arm for it, `C-c C-c' has always installed it, and -;; routing it to the expression evaluator would take that away. +;; `package' is the one declaration `C-x C-e' does not claim, and it is kept +;; out to keep a behaviour rather than to follow a rule: the two keys have +;; always differed here, `C-c C-c' installing a `package' form and `C-x C-e' +;; leaving it to the expression evaluator. Nothing wants changing — a +;; `package' line is written once at the top of a file and is not a thing +;; anybody re-evaluates by hand — so the difference stays, named here where it +;; can be found rather than inferred from a list that is otherwise complete. (defconst flan--defun-heads (cons "package" flan--declaration-heads) "Heads `C-c C-c' recompiles and installs rather than evaluating.") diff --git a/emacs/test-flan.el b/emacs/test-flan.el index c41bbd7..6c218a1 100644 --- a/emacs/test-flan.el +++ b/emacs/test-flan.el @@ -623,12 +623,13 @@ already rely on it — so nothing here is a stand-in for the real thing." (test-flan--check "and draws no value beside it" (null (flan--result-overlays)))) - ;; `package' is the one head the two keys disagree about, and it is a real - ;; disagreement rather than an oversight: `Parse.expr' has no arm refusing - ;; it, so C-x C-e's "would this fail as an expression" says no, while - ;; `Parse.decl' has one and C-c C-c has always installed it. Routing both - ;; off one list would have quietly taken that away. Nothing is sent here — - ;; the claim is only about which path each key picks. + ;; `package' is the one head the two keys disagree about, and the + ;; disagreement is a behaviour being kept rather than a rule being followed: + ;; `C-c C-c' has always installed a `package' form and `C-x C-e' has always + ;; left it to the expression evaluator, and nothing about a line written once + ;; at the top of a file asks for that to change. Routing both keys off one + ;; list would quietly take it away. Nothing is sent here — the claim is only + ;; about which path each key picks. (with-temp-buffer (flan-mode) (insert "(package demo)\n") @@ -642,6 +643,33 @@ already rely on it — so nothing here is a stand-in for the real thing." (test-flan--check "and C-x C-e does not, for the reason it never has" (null (flan--declaration-before-point)))) + ;; And every other head does agree, which is the half that had rotted: the + ;; list was topped up by name and so lost `defdata' and all four object and + ;; dispatch heads for as long as they had existed. A head missing from it + ;; is not a quiet fallback — the form goes to the expression evaluator and + ;; the daemon refuses it with "is a top-level declaration, not an + ;; expression", which is the complaint this is here to keep from coming + ;; back. Read off `Parse.decl', so the check is the derivation. + (with-temp-buffer + (flan-mode) + (dolist (head '("defmacro" "defn" "defvar" "defconst" "defstruct" + "defdata" "defunion" "defenum" "defalias" + "defclass" "defgeneric" "defmulti" "defmethod" + "import" "declare" "declare-c")) + (erase-buffer) + ;; A body neither key needs to be valid: both questions are asked of the + ;; head alone, and a form that parses is a different test. + (insert "(" head " thing [x])\n") + (goto-char (point-min)) + (search-forward "thing") + (test-flan--check (format "C-c C-c installs a %s form" head) + (equal (flan--declaration-head-at + (car (flan--defun-bounds)) flan--defun-heads) + head)) + (goto-char (point-max)) + (test-flan--check (format "and C-x C-e installs one too, for %s" head) + (equal (car (flan--declaration-before-point)) head)))) + ;; A prefix on the expression path is a flag and not a position — there is ;; no inside for one to point at — so both `C-u' and `C-u C-u' mean the one ;; thing, which is `C-u C-x C-e's behaviour reached through the other key.