5

I encountered an issue with the following code, provided by NickD as an answer to a previous question (link to the question):

(defun my-add-string-to-list (LIST-VAR STRING)
  (add-to-list LIST-VAR STRING t)
  (buffer-string)) ;; just to show that the function does not return a list

(defun my-failing-test ()
  (interactive)
  (let ((MYLIST '()))
    (my-add-string-to-list 'MYLIST "foo")
    (my-add-string-to-list 'MYLIST "bar")
    (message (format "%S" MYLIST))))

When I evaluate these functions in Emacs using eval-defun (C-M-x) in the *scratch* buffer and then run M-x my-failing-test, I encounter the following error (this happens also if I start Emacs with emacs -Q):

my-add-string-to-list: Symbol’s value as variable is void: MYLIST

Observations:

  1. If I evaluate the *scratch* buffer with eval-buffer, the my-failing-test function works as expected.

  2. If I save the same code (as suggested by NickD) in a file, for example, /tmp/fool.el, and then evaluate the functions with eval-defun, the my-failing-test function works as expected.

Additional information:

  • The modeline in the *scratch* buffer reads: (Lisp Interaction ElDoc).
  • The modeline in the fool.el buffer reads: (Elisp/d ElDoc).

Why is this happening? Until now, I've always used the scratch buffer to test function prototypes, and I believed that was its purpose. Am I mistaken, or is this some sort of bug?

1 Answer 1

8

Your Emacs version (29, most likely) has silently changed the value of lexical-binding from nil to t in the *scratch* buffer.

When you use C-M-x to evaluate only those functions that variable isn't taken into account (it is nil by default in Emacs generally, still).

When you use M-x eval-buffer that variable's value in the buffer (t) is taken into account.

When lexical-binding is nil, variable MYLIST is automatically a special variable (i.e., dynamically bound). When lexical-binding is nil, variable MYLIST is by default lexically bound, unless you use (defvar MYLIST).

In your first test, there's no dynamic binding for MYLIST because lexical-binding is t in the buffer. In your second test it has a dynamic binding - the let binds with a dynamic binding because lexical-binding is nil.

The doc for add-to-list tells you this about argument LIST-VAR:

LIST-VAR should not refer to a lexical variable.

add-to-list looks for a dynamic binding for LIST-VAR.

Until now, I've always used the scratch buffer to test function prototypes, and I believed that was its purpose.

Yes, and you've counted on it using dynamic binding by default, as we all have. The mode-line indication of Lisp Interaction mode wasn't updated to use the suffix /l or /d to indicate lexical or dynamic binding. The mode-line indication of Elisp mode was updated to provide this info.

The comment text at the start of *scratch* should tell you that the buffer-local value of lexical-binding is t, where it tells you this less-important information:

;; This buffer is for text that is not saved, and for Lisp evaluation.
;; To create a file, visit it with C-x C-f and enter text in its buffer.

Maybe M-x report-emacs-bug to request such an enhancement?

This is a real gotcha, especially for users used to the old behavior in *scratch*. Changing the basic behavior silently is a no-no. (Yes, you can read about the change with C-h n (NEWS), but a clear indication in *scratch* itself would help.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.