4

The following code works fine with M-x eval-buffer, and byte-compiles without any warnings.

(eval-when-compile
  (defconst demo-one 1))

(defvar demo-some-var
  `(foo bar ,demo-one))

However, if I byte compile it, I get broken code:

;ELC
;;; Compiled
;;; in Emacs version 26.1
;;; with all optimizations.

;;; This file uses dynamic docstrings, first added in Emacs 19.29.

;;; This file does not contain utf-8 non-ASCII characters,
;;; and so can be loaded in Emacs versions earlier than 23.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defvar demo-some-var (list 'foo 'bar demo-one))

The byte compiler hasn't put demo-one in the .elc file, so anyone installing my package from (M)ELPA gets broken code.

I should be using eval-and-compile in this case, but I didn't realise my code was wrong. Can the byte compiler warn me here? Why didn't it?

3 Answers 3

3

The byte-compiler attempts to catch such cases w.r.t functions that are defined at compile-time but not at run-time (resulting in warnings along the lines of "These function are not known to be defined at run-time"), but it makes no similar efforts for variables. This probably deserves a feature request.

1
1

For understanding why demo-one is undefined when loading your byte compiled file it is best to recall the main application of eval-when-compile.

The sense of the following code is not to put (require 'my-nifty-macro-package) into the compiled file but to ensure during byte compilation that the macros from my-nifty-macro-package are known, i.e., to load the library my-nifty-macro-package when compilation happens.

(eval-when-compile (require 'my-nifty-macro-package))

Since the macros are already expanded in the byte compiled code the package my-nifty-macro-package is no longer needed for loading the byte compiled file. Therefore one excludes the line (require 'my-nifty-macro-package) from the byte compiled code with eval-when-compile.

You should put the code (defconst demo-one 1) simply into your source file without any additional hocus-pocus. That code is put behind the byte compiled function definitions in the compiled file.

About your actual question

How do I get byte-compilation warnings about undefined variables?

The thing is that you did work very hard to avoid the warning even if demo-one is undefined in the byte compiled file. You defined it during byte compilation and you did explicitly exclude it from the byte compiled code with eval-when-compile.

Remove the eval-when-compile. If you also remove (defconst demo-one 1) and you use demo-one without let-binding you get a warning.

4
  • It's worth mentioning that even if removing the constant definition worked as OP expected, it would have the negative effect of getting rid of documentation.
    – user12563
    Jun 17, 2019 at 20:44
  • @DoMiNeLa10 I am not quite sure what you mean. The OP would use (eval-and-compile (defconst test 1 "Some doc")) which does compile and provide the doc string in the compiled file. (Even if it is not contained in its original form in the compiled file. It is rather contained as constant string that can be re-used.)
    – Tobias
    Jun 17, 2019 at 22:55
  • 1
    @DoMiNeLa10 The worst thing in omitting a defvar or defconst is that the variable is not made special in the concerned file. That can have very strange effects in connection with lexical scoping.
    – Tobias
    Jun 17, 2019 at 22:59
  • Thanks for examples of when eval-when-compile is useful! I agree that defconst on its own is generally better, but I wasn't able to do that here because syntax-propertize-rules produces a compilation warning. Jun 23, 2019 at 18:01
0

It's worth noting that you can get everything you (seem to) want using macros. For instance:

(eval-when-compile (defmacro demo-one () 1))

(defvar demo-some-var `(foo bar ,(demo-one)))

Now, when you construct demo-some-var, the macro will compile and run, and it will be as if you set (defvar demo-some-var '(foo bar 1)) directly. Further more, since your macro will only be used in this compiled context, it is now safe to wrap its entire definition in eval-when-compile, thus removing it from the .elc file.

You should know that defconst doesn't behave like (say) a C preprocessor macro. It is a variable that the Emacs Lisp interpreter must look for. So when you compile it out of existence in eval-when-compile, and then reference it in demo-some-var's definition, this is incorrect.

As to why it works when you run eval-buffer, the eval-when-compile function has the following behavior:

  • It is evaluated into the compiler when compiling a source file.
  • It is not placed into the byte-compiled output when compiling a source file.
  • When evaluated (i.e., no compilation involved, as in eval-buffer), it is always run.

So your the Emacs session in which you ran eval-buffer has the definition of demo-one, but any .elc you compiled, will have it stripped away. For more on eval-when-compile, check the Emacs function documentation with C-h f eval-when-compile, or the online Emacs manual: https://www.gnu.org/software/emacs/manual/html_node/cl/Time-of-Evaluation.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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