GNU bug report logs - #74660
31.0.50; bind-keys has unexpected behavior when evaluated with eval-defun

Previous Next

Package: emacs;

Reported by: Dale <dale <at> codefu.org>

Date: Tue, 3 Dec 2024 00:51:02 UTC

Severity: normal

Found in version 31.0.50

To reply to this bug, email your comments to 74660 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#74660; Package emacs. (Tue, 03 Dec 2024 00:51:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Dale <dale <at> codefu.org>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Tue, 03 Dec 2024 00:51:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Dale <dale <at> codefu.org>
To: bug-gnu-emacs <at> gnu.org
Subject: 31.0.50; bind-keys has unexpected behavior when evaluated with
 eval-defun
Date: Mon, 2 Dec 2024 18:50:38 -0600
Start any Emacs that includes bind-key.el with "emacs -Q".  Then put the
following into an `emacs-lisp-mode' buffer (or anywhere C-M-x is bound
to `eval-defun'):

    (bind-keys :repeat-map foo-map
               ("n" . next-line)
               :exit
               ("q" . ignore))

Put the point anywhere in this form and press C-M-x (`eval-defun') twice.
Now look at the value of `foo-map'.

Expected value: (keymap (113 . ignore) (110 . next-line))
Both bindings are set.

Observed value: (keymap (113 . ignore))
Only the last binding is set.

I believe the problem is that `eval-defun' and friends treat `defvar`
specially via `elisp--eval-defun-1': normally `defvar' won't change the
value of SYMBOL if it is already bound, but `eval-defun' (and
`eval-last-sexp' and probably others) call `elisp--eval-defun-1' to
rewrite the `defvar' into a form that always sets ("re-initializes")
SYMBOL.

Additionally, `elisp--eval-defun-1' will recurse into `progn' forms to
apply this rewriting of `defvar'.

If you macroexpand the `bind-keys' form from my test case, you'll see
that `bind-keys' produces two `defvar' forms for the provided keymap:

    (progn
      (defvar foo-map (make-sparse-keymap))
      (put (function next-line) 'repeat-map 'foo-map)
      (bind-key "n" (function next-line) foo-map nil)
      (defvar foo-map (make-sparse-keymap))
      (bind-key "q" (function ignore) foo-map nil))

Normally this second `defvar' has no effect, but when you `eval-defun'
this `bind-keys' form, the special behavior from `elisp--eval-defun-1'
kicks in for `defvar', and so the second `defvar' clobbers the keymap
value that was set up by the first `defvar'.  (Actually, the first
`defvar' will also clobber the keymap, which may not be what the user
expects.)

You do have to `eval-defun' twice because `elisp--eval-defun-1' only
applies its special behavior when the variable being defined is unbound.
The first time through, `foo-map' is undefined, so `defvar' is left
alone, and the resulting value of `foo-map' is as expected.  The second
time through, `foo-map' is now bound, so the special behavior kicks in,
and both `defvar' forms set `foo-map' to an empty sparse keymap.

Normally I wouldn't open a bug for this, because this special behavior
for `eval-defun' is documented behavior.  However, I think the case of
`bind-keys' is problematic for two reasons:

1. It is not immediately obvious that the `bind-keys' macro is using
   `defvar'.

2. `bind-keys' is something users put in their init file, and so they're
   likely going to be using C-M-x or C-x C-e a lot while they're
   tweaking and testing their init file (exactly what happened to me).

Further notes:

I do find the special treatment of `defvar' (and `defcustom') with C-M-x
and C-x C-e useful, so I'm not proposing removing that. :)

Arguably, `elisp--eval-defun-1' should notice when there are more than
one `defvar' forms for the same variable, and only the first should
exhibit the special "always initialize SYMBOL" behavior.  I don't want
to make that argument, though, because I feel the special-casing of
`defvar' is surprising enough as-is.  I don't want to make that more
complex.

Maybe `bind-keys' shouldn't emit more than one `defvar' form for a
keymap?  Or maybe it shouldn't emit any `defvar' forms if it finds the
keymap is already bound?

Thank you for maintaining Emacs :)


In GNU Emacs 31.0.50 (build 1, aarch64-apple-darwin23.6.0, NS
appkit-2487.70 Version 14.7 (Build 23H124)) of 2024-10-12 built on
daleRepository revision: 05e418e0688f24b5518e38e54bde96a639f7c70d
Repository branch: master
Windowing system distributor 'Apple', version 10.3.2487
System Description:  macOS 14.7.1

Configured using:
 'configure --without-x --with-xwidgets --with-json --with-tree-sitter --without-imagemagick --with-xpm --with-jpeg --with-tiff --with-gif --with-png --with-rsvg --with-webp --with-sqlite3 --with-lcms2 --with-cairo --with-xml2 --with-gnutls --with-zlib --with-modules --with-threads --with-native-compilation --with-ns --enable-ns-self-contained 'CFLAGS= -D_DARWIN_UNLIMITED_SELECT=1 -DFD_SETSIZE=10240''





This bug report was last modified 224 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.