Subject: bug#54161: 27.2; `define-minor-mode' with alist of key bindings

From: Drew Adams <[email-masked]>
Date: Fri, 25 Feb 2022 17:48:41 +0000

Apologies for this bug report, as I imagine it must be a duplicate, but
I couldn't find the dup.

And I imagine that there's no bug in behavior, and I'm just
misunderstanding the doc.  (The behavior is longstanding across Emacs
releases.)

In that case, maybe the doc could benefit from some rewording?  I've
reread it a few times now, and I haven't figured out what I'm
misreading.

emacs -Q

These, and similar variants (e.g. using keyword :keymap) produce a
keymap that defines a binding for command `forward-char' with prefix key
`C', followed by `-', followed by `o'.  I would expect them to instead
bind the command to key `C-o'.

(define-minor-mode tata-mode
  "TATA MODE" nil nil '(("\\C-o" . forward-char)))

(define-minor-mode titi-mode
  "TITI MODE" nil nil '(((kbd "C-o") . forward-char)))

Digging into the expansion of `define-minor-mode' I see that those sexps
expand these sexps to produce the keymaps:

(easy-mmode-define-keymap '(("\\C-o" . forward-char)))
(easy-mmode-define-keymap '(((kbd "C-o") . forward-char)))

And those produce this keymap:

(keymap (67 keymap (45 keymap (111 . forward-char))))

That is,

(keymap (?C keymap (?- keymap (?o . forward-char))))

The doc (both Elisp manual and doc string) says this:

 The optional argument KEYMAP specifies the keymap for the minor
 mode.  If non-'nil', it should be a variable name (whose value is a
 keymap), a keymap, or an alist of the form

      (KEY-SEQUENCE . DEFINITION)

 where each KEY-SEQUENCE and DEFINITION are arguments suitable for
 passing to 'define-key'.

I think that's the case in these examples, no?  Both (kbd "C-o") and
"\C-o" are suitable args for `define-key'.

What am I missing?

I searched the Elisp sources and noticed only one occurrence of using an
explicit alist, in refill.el:

 :keymap '(("\177" . backward-delete-char-untabify))

And indeed, if I use this, which has a literal Control-O character,
there's no problem (key `C-o' is bound to `forward-char):

(define-minor-mode toto-mode
  "TOTO MODE" nil nil '(("^O" . forward-char)))

(The Control-O char won't pass through email, so I've substituted the
string "^O", but it is actually a string with just a Control-O char.)

In GNU Emacs 27.2 (build 1, x86_64-w64-mingw32)
 of 2021-03-26 built on CIRROCUMULUS
Repository revision: deef5efafb70f4b171265b896505b92b6eef24e6
Repository branch: HEAD
Windowing system distributor 'Microsoft Corp.', version 10.0.19043
System Description: Microsoft Windows 10 Pro (v10.0.2009.19043.1526)