4

I think I found a bug in defmacro so I'd like to ask for confirmation before I make a fool of myself reporting something silly!

Here is how to reproduce it:

  1. Define the following macro which simply reports the value of case-fold-search in the minibuffer:
      (defmacro report-case-fold-search ()
        (let ((cfs case-fold-search))
          `(message "%s" ,cfs)))
  1. Execute it several times with randomly alternating values of case-fold-search:
      (progn (setq case-fold-search nil) (report-case-fold-search))
      (progn (setq case-fold-search t) (report-case-fold-search))

The output I get seems to indicate that there is a delay in the sense that each time I go from one to the other, it takes two tries to get the expected output.

  1. To be more precise, execute the following at least 3 times in a row:
      (progn (setq case-fold-search nil) (report-case-fold-search))
  1. Then execute once:
      (progn (setq case-fold-search t) (report-case-fold-search))
  1. This time a get nil for an answer even though case-fold-search has just been set to t.

Question. Can anyone reproduce this? If so, is there a sensible explanation for it? Otherwise, should I report it as a bug?

Here is my emacs version:

GNU Emacs 28.2 (build 2, x86_64-pc-linux-gnu, GTK+ Version 3.24.20, cairo version 1.16.0) of 2022-12-08

Drew
  • 75,699
  • 9
  • 109
  • 225
Ruy
  • 787
  • 4
  • 11
  • 3
    In most cases you would like to use `defun`, it defines a normal function. `defmacro` on the other hand might look the same but returns a replacement expression, and it's a lot harder to get right. However, if you really want to use a macro I would recommend the `macrostep` package which lets you see what a macro would expand to in the context where it is used. – Lindydancer Jan 12 '23 at 07:24

1 Answers1

11

There's no bug.

Macros are normally expanded ahead of time (typically during byte-compilation or loading, but "not at eval-time" is the key assumption that you need to make), so any code you're evaluating as part of their expansion gets evaluated at whatever indeterminate time that expansion happens. For code which is evaluated interactively, the macro expansion does happen at that time, but it's still before the eval phase: firstly the code is read, then all macros in the code are expanded until none remain, and finally the result is evaluated.

In your case, the let binding happens only at expansion time, with the resulting value (of whatever case-fold-search was when expansion took place) being hard-coded into the expanded code that actually gets evaluated in place of the macro call.

If you were to repeatedly evaluate your progn interactively then macro expansion would happen each time, but it would always be the previous value of case-fold-search which was hard-coded for evaluation, because the setq doesn't happen until later, during the eval phase.

If you wanted the expanded code to take the eval-time value of case-fold-search into account, you would write this:

(defmacro report-case-fold-search ()
  `(let ((cfs case-fold-search))
     (message "%s" cfs)))

Although if that's a real example, you would just use:

(defmacro report-case-fold-search ()
  `(message "%s" case-fold-search))

There's no particular reason for it to be a macro at all, though, so a function would be preferable.

You will see some macros processing values at expansion time, but in such cases the values will usually be (only) the unevaluated arguments to the macro. Dealing with anything besides those arguments at expansion time is quite likely to be a bug.

phils
  • 48,657
  • 3
  • 76
  • 115
  • 1
    I think you have an extra comma in the first example. – Lindydancer Jan 12 '23 at 07:22
  • 1
    Indeed; thank you @Lindydancer. Fixed now. – phils Jan 12 '23 at 10:56
  • Thanks for your answer. That makes me very curious about the precise time the expansion is made. It seems hard to believe it takes place **before** the setting of `case-fold-search`. – Ruy Jan 12 '23 at 12:56
  • One more comment: my question is derived from a much more complicated example, where the apparent error appeared, which in turn was reduced to the bare minimum to highlight the phenomenon. In particular, the use of `defmacro` is fundamental. – Ruy Jan 12 '23 at 13:03
  • Regarding my last comment on expansipn times, why would the return value change between two successive executions if nothing else was changed? – Ruy Jan 12 '23 at 13:23
  • 1
    I've updated the initial paragraphs to cover interactive evaluations. – phils Jan 12 '23 at 23:27
  • And again, you really don't want to be making any assumptions about the "precise time" that macro expansion happens, because it happens at different times in different circumstances. As such, macro definitions generally need be written so that the *precise* expansion time has no bearing on their output. – phils Jan 12 '23 at 23:54
  • 1
    And to further emphasise that last comment... when it comes to byte-compiled libraries, the macros were likely to have been expanded during some previous Emacs session rather than the current one (and/or in a separate asynchronous session if native-compilation is happening); and if you installed Emacs from an OS package manager then the macros for the core code were expanded on someone else's computer! – phils Jan 13 '23 at 02:10
  • Great answer -- – Drew Jul 05 '23 at 20:03