bug#81602: 32.0.50; Is this a bug? setopt on a not-yet-loaded option breaks loading the file that defines it

Rahul Martim Juliato <[email protected]>
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
Hello there!

I noticed some strange errors after trying to compile from master today.

I'm not sure whether this is a bug, although I suspect it might be, so I
thought I'd ask and share my findings.

My init.el sets (setopt eshell-prompt-function (lambda () ...)) at top
level.  Since commit 7e9e10d9842, the first M-x eshell of the session
gives me:

  Error (eshell): Unable to load Eshell module ‘eshell-prompt’

and an Eshell buffer with no prompt.  If I kill that buffer and run M-x
eshell again, everything works, which is what kept me looking in the
wrong place for a while.

Here is the same failure without my config:

  emacs -Q --batch --eval '(progn (setopt eshell-prompt-function (lambda () "$ ")) (require (quote em-prompt)))'
  => Wrong type argument: listp, #[nil ("$ ") (t)]

A symbol or t breaks the same way:

  emacs -Q --batch --eval '(progn (setopt erc-sasl-mechanism (quote plain)) (require (quote erc-sasl)))'
  => Wrong type argument: listp, plain

  emacs -Q --batch --eval '(progn (setopt vc-dir-auto-hide-up-to-date t) (require (quote vc-dir)))'
  => Wrong type argument: listp, t

What I think happens: when I call `setopt' before the defcustom has run,
`setopt--set' cannot type-check yet, so it stashes the value for later.

  ;; cus-edit.el
  (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))
	...)

Later, `custom-declare-variable' picks the stash up again, and reads
each entry with `car':

  ;; custom.el
  (dolist (value (prog1 (nreverse (get symbol 'custom-check-values))
				   (put symbol 'custom-check-values nil)))
	(let ((type (get symbol 'custom-type)))
	  (when (and type
				 (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))))

`setopt--set' pushes the bare value, `custom-declare-variable' reads it
as if someone had wrapped it in a list.  My lambda is not a list, so
`car' signals in the middle of evaluating the defcustom, and `load' of
em-prompt.el gives up right there.  A list value survives, though it
type-checks the list's car instead of the value I passed.

That `car' looks llike a leftover to me.  Before 7e9e10d9842,
`setopt--set' did (put variable 'custom-check-value (list value)) and
`custom-initialize-reset' read (car value), which matched.  The commit
renamed the property to `custom-check-values', switched it to a list of
bare values, moved the check into `custom-declare-variable', and kept
the `car'.  I found nothing in etc/NEWS about the storage format, so I
assume this was not on purpose.

Which side holds the contract?  If it is the wrapper, then cus-edit.el
needs one word:

  --- a/lisp/cus-edit.el
  +++ b/lisp/cus-edit.el
  @@ setopt--set
  -        (push value (get variable 'custom-check-values))
  +        (push (list value) (get variable 'custom-check-values))

If instead the stash should hold bare values, then I think custom.el is
the side to touch: maybe :match against VALUE, and print VALUE in the
warning rather than the wrapper.

A check that only wants to print a warning can currently abort the load
of the file it is checking.  Wrapping the dolist in
`with-demoted-errors' would keep a bad stashed value from taking the
file down with it.

Two details that made this hard to see from the Eshell side.
`eshell-load-modules' wraps the load in `condition-case-unless-debug',
so the wrong-type-argument never reaches me and I get the warning above
instead.  And `custom-declare-variable' clears `custom-check-values'
with `prog1' before the check signals, so nothing is pending on the
second try:

  (setopt eshell-prompt-function (lambda () "$ "))
  (get 'eshell-prompt-function 'custom-check-values)
  => (#[nil ("$ ") (t)])
  (require 'em-prompt)             ;; signals wrong-type-argument
  (get 'eshell-prompt-function 'custom-check-values)
  => nil
  (featurep 'em-prompt)
  => nil
  (require 'em-prompt)             ;; works, and the evidence is gone

Any option that has no `custom-type' yet when `setopt' runs looks like a
candidate, so the same config can carry several of these waiting to
fire, one per file.  I tried this to list the 'pending ones' in a
running Emacs:

  (let (found)
	(mapatoms (lambda (s)
				(dolist (v (get s 'custom-check-values))
				  (unless (listp v) (push s found)))))
	found)


Thanks,

-- Rahul

---


In GNU Emacs 32.0.50 (build 10, aarch64-apple-darwin25.5.0, NS
 appkit-2685.60 Version 26.5.2 (Build 25F84)) of 2026-08-11 built on
 MacBook-Pro.local
Repository revision: 902f8eb6334b49e08d7dd6ba6e4b4036175bd889
Repository branch: master
Windowing system distributor 'Apple', version 10.3.2685
System Description: macOS 26.5.2

Configured using:
 'configure --with-native-compilation=aot --with-tree-sitter --with-gif
 --with-png --with-jpeg --with-rsvg --with-tiff --with-imagemagick
 --with-mailutils --with-gnutls'

Configured features:
ACL DBUS GLIB GNUTLS IMAGEMAGICK LCMS2 LIBXML2 MODULES NATIVE_COMP
NOTIFY KQUEUE NS PDUMPER PNG RSVG SQLITE3 THREADS TOOLKIT_SCROLL_BARS
TREE_SITTER WEBP XIM ZLIB

Important settings:
  value of $LANG: pt_BR.UTF-8
  locale-coding-system: utf-8-unix

Major mode: Summary

Minor modes in effect:
  global-auto-revert-mode: t
  electric-pair-mode: t
  which-key-mode: t
  emacs-solo/center-document-mode: t
  minibuffer-electric-default-mode: t
  minibuffer-depth-indicate-mode: t
  display-time-mode: t
  xterm-mouse-mode: t
  winner-mode: t
  save-place-mode: t
  savehist-mode: t
  repeat-mode: t
  recentf-mode: t
  tty-tip-mode: t
  pixel-scroll-precision-mode: t
  global-goto-address-mode: t
  goto-address-mode: t
  delete-selection-mode: t
  override-global-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  vc-auto-revert-mode: t
  mouse-wheel-mode: t
  tab-bar-history-mode: t
  tab-bar-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  minibuffer-nonselected-mode: t
  minibuffer-regexp-mode: t
  buffer-read-only: t
  column-number-mode: t
  line-number-mode: t
  indent-tabs-mode: t
  transient-mark-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t

Load-path shadows:
/Users/rmj/Projects/github/emacs-solo/lisp/elpher hides /Users/rmj/Projects/github/emacs-solo/user-lisp/elpher

Features:
(shadow emacsbug network-stream url-http url-gw nsm url-cache url-auth
gnus-cite smiley mm-archive mail-extr qp textsec uni-scripts
idna-mapping uni-confusable textsec-check gnus-bcklg sort gnus-ml
gnus-topic nndraft nnmh nnmaildir nnnil gnus-agent gnus-srvr gnus-score
score-mode nnvirtual gnus-msg gnus-art mm-uu mml2015 mm-view mml-smime
smime gnutls dig gnus-cache help-fns emacs-news-mode tramp-cache
tramp-sh whitespace misearch multi-isearch em-prompt byte-opt
cursor-sensor private .gnus gnus-async nntp gnus-sum shr pixel-fill
kinsoku url-file gnus-group gnus-undo gnus-start gnus-cloud nnimap
nnmail mail-source utf7 nnoo gnus-spec gnus-int gnus-range message
sendmail yank-media puny rfc822 mml mml-sec epa epg rfc6068 epg-config
mm-decode mm-bodies mm-encode mail-parse rfc2231 rfc2047 rfc2045
ietf-drums mailabbrev gmm-utils mailheader gnus-win help-at-pt em-unix
em-term term ehelp em-script em-pred em-ls em-hist em-glob em-extpipe
em-cmpl em-dirs em-basic em-banner em-alias esh-mode esh-var log-edit
pcvs-util add-log vc eshell esh-cmd generator esh-ext esh-proc esh-opt
esh-io esh-arg esh-module esh-module-loaddefs esh-util autorevert
display-line-numbers eglot external-completion jsonrpc diff diff-mode
track-changes ert ewoc debug backtrace find-func filenotify imenu
time-stamp tramp trampver tramp-integration tramp-message tramp-compat
shell pcomplete parse-time iso8601 format-spec tramp-loaddefs checkdoc
elec-pair which-key init emacs-solo-gh emacs-solo-yt
emacs-solo-erc-image emacs-solo-flymake-languagetool
emacs-solo-flymake-eslint emacs-solo-khal emacs-solo-khard
emacs-solo-eldoc-box emacs-solo-clipboard emacs-solo-m3u
emacs-solo-container emacs-solo-icons-eshell emacs-solo-icons-ibuffer
ibuf-macs emacs-solo-icons-dired emacs-solo-icons emacs-solo-dired-mpv
emacs-solo-dired-gutter emacs-solo-ai emacs-solo-how-in emacs-solo-rate
emacs-solo-osm svg dom image-dired image-dired-tags image-dired-external
image-dired-util image-mode exif dired dired-loaddefs emacs-solo-weather
emacs-solo-replace-as-diff emacs-solo-sudo-edit emacs-solo-cl flymake
warnings emacs-solo-smash emacs-solo-temp-sharing emacs-solo-olivetti
emacs-solo-ace-window emacs-solo-gutter vc-git vc-dispatcher
emacs-solo-highlight-keywords emacs-solo-viper-extensions
emacs-solo-project-select emacs-solo-rainbow-delimiters
emacs-solo-exec-path-from-shell emacs-solo-mode-line
emacs-solo-transparency emacs-solo-formatter emacs-solo-movements
emacs-solo-themes kusanagi-theme modus-themes derived color pcase
markdown-ts-mode noutline outline xref project finder-inf package
browse-url xdg url url-proxy url-privacy url-expand url-methods
url-history url-cookie url-domsuf url-util mailcap url-handlers
url-parse auth-source eieio eieio-core password-cache json map url-vars
speedbar ezimage dframe minibuf-eldef mb-depth gnus nnheader gnus-util
time-date mail-utils range mm-util mail-prsvr disp-table files-x time
xt-mouse winner saveplace savehist repeat recentf tree-widget tty-tip
grep compile text-property-search comint ansi-osc ansi-color treesit
pixel-scroll ring goto-addr thingatpt delsel edmacro kmacro cl-macs gv
cl-extra help-mode use-package-bind-key bind-key easy-mmode
use-package-ensure cl-seq use-package-core .user-lisp-autoloads battery
dbus xml subr-x comp-run comp-common rx loaddefs-gen generate-lisp-file
lisp-mnt radix-tree bytecomp byte-compile ghostel-autoloads
vterm-autoloads package-activate early-init cus-edit pp cus-start
cus-load icons wid-edit cl-loaddefs cl-lib rmc iso-transl tooltip cconv
eldoc paren electric uniquify ediff-hook vc-hooks lisp-float-type
elisp-mode mwheel term/ns-win ns-win ucs-normalize mule-util
term/common-win tool-bar dnd fontset image regexp-opt fringe
tabulated-list replace newcomment text-mode lisp-mode prog-mode register
page tab-bar menu-bar rfn-eshadow isearch easymenu timer select
scroll-bar mouse jit-lock font-lock syntax font-core term/tty-colors
frame minibuffer nadvice seq simple cl-generic indonesian philippine
cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao
korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech
european ethiopic indian cyrillic chinese composite emoji-zwj charscript
charprop case-table epa-hook jka-cmpr-hook help abbrev obarray oclosure
cl-preloaded button loaddefs theme-loaddefs faces cus-face macroexp
files window text-properties overlay sha1 md5 base64 format env
code-pages mule custom widget keymap hashtable-print-readable backquote
threads dbusbind kqueue cocoa ns lcms2 multi-tty make-network-process
tty-child-frames native-compile emacs)

Memory information:
((conses 16 824468 1682763) (symbols 48 33687 13)
 (strings 32 268868 468281) (string-bytes 1 13543447)
 (vectors 16 105411) (vector-slots 8 2186008 654062)
 (floats 8 603 14623) (intervals 56 36215 4176) (buffers 1064 39))
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.