Subject: bug#19383: 25.0.50; query-replace-regexp becomes unusable when using lisp expressions for replacement

From: Dima Kogan <[email-masked]>
Date: Mon, 15 Dec 2014 01:01:28 -0800

Hi.

I'm using a bleeding-edge emacs build, compiled from latest source as of
Dec 14. There's a regression in query-replace-regexp where under some
conditions it becomes unable to parse its history, and becomes unusable.

Here's a recipe. Deviating from the exact steps can make the failure not
occur, but I do see this in everyday use, so this isn't exactly a corner
case.

1. Create a file called /tmp/dat. I don't think the contents are super
important. I have

0001
0002
0003
0004
0005
0006
0007
0008

2. emacs -Q /tmp/dat

3. C-M-%
   ^.
   <return>
   \,(concat \#&)
   <return>

   Here we try to do a replacement, but there's an error: (concat ...)
   expects a string, but \#& is a number, and emacs complains. This is
   fine.

4. C-M-%
   <up>
   <end>
   <left>
   <left>
   <backspace>
   <return>
   SPC

   We run another replacement. We pull the previous one from the
   history, and manually remove the # to fix the complaint. \& is a
   string, as desired. We then replace the first match by presing SPC

5. M-%

   Error!

After step 5, trying to M-% or C-M-% always fails with

 mapconcat: Wrong type argument: number-or-marker-p, replace-eval-replacement

This is irreparable without dipping into lisp. Resetting the history
fixes it for the session: (setq query-replace-defaults nil)

When broken, the value of query-replace-defaults is

 (("" replace-eval-replacement replace-quote (concat (match-string 0))) ("^." . "\\,(concat \\#&)"))

and the value of query-replace-history is

 ((replace-eval-replacement replace-quote (concat (match-string 0))) "" "\\,(concat \\#&)" "^.")

The backtrace looks like this:

 Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p replace-eval-replacement)
   isearch-text-char-description(replace-eval-replacement)
   mapconcat(isearch-text-char-description (replace-eval-replacement replace-quote (concat (match-string 0))) "")
   query-replace-descr((replace-eval-replacement replace-quote (concat (match-string 0))))
   #[(from-to) "\302@!	\302A!Q\207" [from-to separator query-replace-descr] 4](("" replace-eval-replacement replace-quote (concat (match-string 0))))
   mapcar(#[(from-to) "\302@!	\302A!Q\207" [from-to separator query-replace-descr] 4] (("" replace-eval-replacement replace-quote (concat (match-string 0))) ("^." . "\\,(concat \\#&)")))
   query-replace-read-from("Query replace" nil)
   query-replace-read-args("Query replace" nil)
   (let ((common (query-replace-read-args (concat "Query replace" (if current-prefix-arg (if (eq current-prefix-arg ...) " backward" " word") "") (if (and transient-mark-mode mark-active) " in region" "")) nil))) (list (nth 0 common) (nth 1 common) (nth 2 common) (if (and transient-mark-mode mark-active) (region-beginning)) (if (and transient-mark-mode mark-active) (region-end)) (nth 3 common)))
   call-interactively(query-replace nil nil)
   command-execute(query-replace)

The issue is that isearch-text-char-description expects a string
argument, but it's getting bits of that s-expression; specifically
'replace-eval-replacement. I haven't looked further, and I don't know if
this value of the history variables is wrong, or if their parsing is
incorrect in some way.

Thanks