I am writing some lisp code and I would like it not to mess with the global variables in the system. I am therefore being very careful to only use variables within the scope of the let special form.

However, as the code gets longer, it becomes harder to make sure this principle is being fully respected, especially since there is the risk of a mispelled variable suddently becoming global in case the incorrect spelling turns the name of a variable into one not bound in any let form.

Ideally it would be great to be able to simply prohibit new global variables to be setq, say with a lisp command inhibit-setting-global-variables-not-already-defined. My question is thus:

Question. Is it possible to automatically verify that a piece of lisp code does not create any new global variables?


1 Answer 1


Is it possible to automatically verify that a piece of lisp code does not create any new global variables?

Turn on lexical-binding:

;;; foo.el --- just frobnicating some foo -*- lexical-binding: t -*-

(setq foo-bar nil)

(defun foo-bar ()
  (let (x)
    (setq y nil)))

;;; foo.el ends here

and then the byte-compiler will do the work for you:

emacs -Q -batch -f batch-byte-compile foo.el

In toplevel form:
foo.el:3:7: Warning: assignment to free variable ‘foo-bar’
foo.el:5:1: Warning: Unused lexical variable ‘x’

In foo-bar:
foo.el:7:11: Warning: assignment to free variable ‘y’

If you want to be particularly strict, you can turn warnings into errors:

emacs -Q -batch -eval '(setq byte-compile-error-on-warn t)' -f batch-byte-compile foo.el

In toplevel form:
foo.el:3:7: Error: assignment to free variable ‘foo-bar’

To see these warnings interactively, turn on flymake-mode.

To reduce the chance of typos to begin with, you can use symbol completion (C-M-i - completion-at-point), dynamic abbreviations (M-/ - dabbrev-expand), or similar.

  • Thanks @Basil! I couldn't ask for a better answer!
    – Ruy
    Commented Feb 12, 2021 at 23:47
  • 1
    @Ruy: Note that without lexical binding, all of your let-bound variables were global (with dynamic scope).
    – phils
    Commented Feb 13, 2021 at 5:32
  • Hi @Basil, your suggestion is working wonders but I'm seeing some strange warnings when byte-compiling my file. Here are some examples: ;;; test.el -*- lexical-binding: t -*- (loop for x in (list 1 2 3) collect (* 2 x)) (let ((animal 'dog)) (case animal ('tiger "Feline") ('dog "Canine") (t "Other") ) ) ;;; test.el ends here
    – Ruy
    Commented Feb 14, 2021 at 23:05
  • I get lots of errors! In toplevel form: test.el:3:7:Warning: reference to free variable ‘for’ test.el:3:11:Warning: reference to free variable ‘x’ test.el:3:13:Warning: reference to free variable ‘in’ test.el:4:3:Warning: reference to free variable ‘collect’ test.el:6:1:Warning: ‘(quote tiger)’ is a malformed function test.el:6:1:Warning: ‘(quote dog)’ is a malformed function test.el:7:9:Warning: ‘t’ called as a function In end of data: test.el:17:1:Warning: the following functions are not known to be defined: What's wrong?
    – Ruy
    Commented Feb 14, 2021 at 23:09
  • 1
    The loop issue aside, lexical binding affects the entire library, and you definitely want to do some reading about the difference between lexical binding and dynamic binding, because code written for (only) dynamic binding does not necessarily work as-is if you enable lexical binding for that library.
    – phils
    Commented Feb 17, 2021 at 8:52

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.