(custom-declare-variable): Check values previously set via setopt

Rename `custom-check-value` to  `custom-check-values` and make it hold
a list of values whose type-check is pending.  Check it in
`custom-declare-variable` rather than `custom-initialize-reset` to fix
bug#81372.

* lisp/custom.el (custom-initialize-reset): Don't type check previous
setopt values.
(custom-declare-variable): Do it here instead.  Change warning message
to clarify that the erroneous value was provided in some earlier setopt call.

* lisp/cus-edit.el (setop--set): Push to the `custom-check-values` only if
we can't do the type check yet, to avoid duplicate warnings (and memory
leaks).
(setopt--set-local): Fix indentation and add FIXMEs.
This commit is contained in:
Stefan Monnier 2026-08-11 12:28:04 -04:00
parent 65b1e84397
commit 7e9e10d984
2 changed files with 36 additions and 32 deletions

View file

@ -1098,10 +1098,12 @@ even if it doesn't match the type.)
(defun setopt--set (variable value)
(custom-load-symbol variable)
;; Check that the type is correct.
(when-let* ((type (get variable 'custom-type)))
(unless (widget-apply (widget-convert type) :match value)
(warn "Value does not match %S's type `%S': %S" variable type value)))
(put variable 'custom-check-value (list value))
(let* ((type (get variable 'custom-type)))
(if (not (or type (get variable 'standard-value)))
;; `custom-declare-variable' has not run yet. Postpone the check.
(push value (get variable 'custom-check-values))
(unless (widget-apply (widget-convert type) :match value)
(warn "Value does not match %S's type `%S': %S" variable type value))))
(funcall (or (get variable 'custom-set) #'set-default) variable value))
;;;###autoload
@ -1157,24 +1159,28 @@ Consult `setopt-local-type-mismatch'."
(let ((accept t))
;; Check that the type is correct.
(when-let* ((type (get variable 'custom-type)))
;; FIXME: If the var hasn't been initialized yet, `type' is nil and we
;; skip the type check altogether. Use `custom-check-values'?
(unless (widget-apply (widget-convert type) :match value)
(let ((msg (format-message
"Value does not match %S's type `%S': %S"
variable type value)))
(cond
;; Fall through and try anyway.
((eq setopt-local-type-mismatch 'accept))
;; Silently discard the mismatched value.
((eq setopt-local-type-mismatch 'discard)
(setq accept nil))
;; Prompt to accept or discard the value.
(setopt-local-type-mismatch
(setq accept (eq ?a (car
(read-multiple-choice msg
'((?a "accept" "Accept")
(?d "discard" "Discard")))))))
(t
(warn msg))))))
;; FIXME: It's weird to do this `setopt-local-type-mismatch`
;; control for `setopt-local' and not for `setopt'.
(cond
;; Fall through and try anyway.
((eq setopt-local-type-mismatch 'accept))
;; Silently discard the mismatched value.
((eq setopt-local-type-mismatch 'discard)
(setq accept nil))
;; Prompt to accept or discard the value.
(setopt-local-type-mismatch
(setq accept (eq ?a (car (read-multiple-choice
msg
'((?a "accept" "Accept")
(?d "discard" "Discard")))))))
(t
(warn msg))))))
(when accept
(condition-case _
(funcall (or (get variable 'custom-set)

View file

@ -91,20 +91,6 @@ The value is either the symbol's current value
(as obtained using the `:get' function), if any,
or the value in the symbol's `saved-value' property if any,
or (last of all) the value of EXP."
;; If this value has been set with `setopt' (for instance in
;; ~/.emacs), we didn't necessarily know the type of the user option
;; then. So check now, and issue a warning if it's wrong.
(let ((value (get symbol 'custom-check-value)))
(when value
(let ((type (get symbol 'custom-type)))
(when (and type
(boundp symbol)
(eq (car value) (symbol-value symbol))
;; Check that the type is correct.
(not (widget-apply (widget-convert type)
:match (car value))))
(warn "Value `%S' for `%s' does not match type %s"
value symbol type)))))
(funcall (or (get symbol 'custom-set) #'set-default-toplevel-value)
symbol
(condition-case nil
@ -245,6 +231,18 @@ set to nil, as the value is no longer rogue."
;; as set the special-variable-p flag.
(internal--define-uninitialized-variable symbol doc)
(put symbol 'custom-requests requests)
;; If this value has been set with `setopt' (for instance in
;; ~/.emacs), we didn't necessarily know the type of the user option
;; then. So check now, and issue a warning if it's wrong.
(dolist (value (prog1 (nreverse (get symbol 'custom-check-values))
(put symbol 'custom-check-values nil)))
(let ((type (get symbol 'custom-type)))
(when (and type
;; Check that the type is correct.
(not (widget-apply (widget-convert type)
:match (car value))))
(warn "Value previously set by setopt did not match %S's type %S:\n%S"
symbol type value))))
;; Do the actual initialization.
(unless custom-dont-initialize
(funcall initialize symbol default)