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.
This commit is contained in:
Joseph Ferano 2026-09-20 18:23:33 +07:00
parent f4fcbe7745
commit 865c7e0445
2 changed files with 68 additions and 30 deletions

View File

@ -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 ;; 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. ;; `(+ 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 ;; Hardcoded, which is the thing to be careful about: the authority is
;; declaration arm of `Parse.expr' in lib/parse.ml (the "is a top-level ;; `Parse.decl' in lib/parse.ml — every head it has an arm for is a
;; declaration, not an expression" refusal, around line 458), plus the ;; declaration and belongs here — and this list is that set minus `package',
;; `declare'/`declare-c' arm a few forms below it. This list is that set and ;; for the reason below. Re-derived whole when it is touched, rather than
;; has to be changed with it. `defunion' is being renamed `defdata', with ;; topped up a name at a time: patching in the one name somebody noticed is
;; `defunion' becoming a C-style untagged union; both spellings are top-level ;; exactly how it came to be missing `defdata' and all four of the object and
;; declarations either way, so this list wants `defdata' adding when that ;; dispatch heads for weeks after they landed. (`Parse.decl's top-level
;; lands rather than a swap. ;; `(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 ;; It used to be derived from somewhere else: the declaration arm of
;; `flan--definers' has it. This is the set of heads that *fail* when sent to ;; `Parse.expr', the "is a top-level declaration, not an expression" refusal,
;; the expression evaluator, which is the bug `C-x C-e' was fixed for, and a ;; on the reading that this is the set of heads that *fail* when sent to the
;; `package' form does not fail — `Parse.expr' has no arm refusing it. It is ;; expression evaluator. That reading does not survive `declare' and
;; still a declaration, so `C-c C-c' has to keep installing it; see ;; `declare-c', which have been in this list from the start and which
;; `flan--defun-heads' below, which is this list plus that one head. ;; `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 (defconst flan--declaration-heads
'("defmacro" "defn" "defvar" "defconst" "defstruct" "defunion" "defenum" '("defmacro" "defn" "defvar" "defconst"
"defalias" "import" "declare" "declare-c") "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.") "Heads whose form is a declaration, and never an expression.")
;; The two keys ask *nearly* the same question, and `package' is where the two ;; `package' is the one declaration `C-x C-e' does not claim, and it is kept
;; readings come apart. `C-x C-e' asks "would this fail if I sent it to the ;; out to keep a behaviour rather than to follow a rule: the two keys have
;; expression evaluator?", and a `package' form would not: `Parse.expr' has no ;; always differed here, `C-c C-c' installing a `package' form and `C-x C-e'
;; arm refusing it, so it falls through to a call of an unknown name and the ;; leaving it to the expression evaluator. Nothing wants changing — a
;; message would be a worse one than the declaration path gives. `C-c C-c' ;; `package' line is written once at the top of a file and is not a thing
;; asks the plain question — "is this a declaration?" — and `package' is one: ;; anybody re-evaluates by hand — so the difference stays, named here where it
;; `Parse.decl' has an arm for it, `C-c C-c' has always installed it, and ;; can be found rather than inferred from a list that is otherwise complete.
;; routing it to the expression evaluator would take that away.
(defconst flan--defun-heads (cons "package" flan--declaration-heads) (defconst flan--defun-heads (cons "package" flan--declaration-heads)
"Heads `C-c C-c' recompiles and installs rather than evaluating.") "Heads `C-c C-c' recompiles and installs rather than evaluating.")

View File

@ -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" (test-flan--check "and draws no value beside it"
(null (flan--result-overlays)))) (null (flan--result-overlays))))
;; `package' is the one head the two keys disagree about, and it is a real ;; `package' is the one head the two keys disagree about, and the
;; disagreement rather than an oversight: `Parse.expr' has no arm refusing ;; disagreement is a behaviour being kept rather than a rule being followed:
;; it, so C-x C-e's "would this fail as an expression" says no, while ;; `C-c C-c' has always installed a `package' form and `C-x C-e' has always
;; `Parse.decl' has one and C-c C-c has always installed it. Routing both ;; left it to the expression evaluator, and nothing about a line written once
;; off one list would have quietly taken that away. Nothing is sent here — ;; at the top of a file asks for that to change. Routing both keys off one
;; the claim is only about which path each key picks. ;; list would quietly take it away. Nothing is sent here — the claim is only
;; about which path each key picks.
(with-temp-buffer (with-temp-buffer
(flan-mode) (flan-mode)
(insert "(package demo)\n") (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" (test-flan--check "and C-x C-e does not, for the reason it never has"
(null (flan--declaration-before-point)))) (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 ;; 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 ;; 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. ;; thing, which is `C-u C-x C-e's behaviour reached through the other key.