Over on the Fediverse, I said something a bit weird (and possibly incoherent and wrong) :
My GNU Emacs tiny kingdom for something that only ran at byte-compile time purely so I can pacify ELisp checker errors that only happen then (in a file that is not byte compiled otherwise), and which can't be guarded with 'eval-while-compile' because then they get run twice.
(It's complicated. This file is load-file'd by my .emacs and needs macros from third party packages to pass type checking.)
I will skip ahead to the solution; since I normally use Flycheck , I fixed this by setting
flycheck-emacs-lisp-initialize-packages
to
t
, which works in my particular setup and for what I do with Emacs Lisp (but might not for you). The rest of this entry is the background explanation.
A while back I split up my .emacs , and some of the split was moving related blocks of
use-package
stuff to a number of separate files and then pulling each file in with '
load-file
'. In some of the use-package blocks I have functions related to the package in either
:init
or
:config
sections. For example:
(use-package marginalia
:init
(defun marginalia-annotate-variable-docstring (cand)
"Annotate variable CAND with only its documentation string."
(when-let* ((sym (intern-soft cand)))
(marginalia--fields
((or (documentation-property sym 'variable-documentation)
(marginalia--definition-prefix sym))
:truncate 1.0 :face 'marginalia-documentation))))
[...]
When you're writing GNU Emacs Lisp code it's very handy to use either Flycheck or Flymake ( I prefer Flycheck ) to get diagnostics, so you can spot problems in advance. This works by running the Emacs Lisp byte compiler on your ELisp file and reporting any warnings and so on that it emits.
In general, to get a startup file like this to check properly, you need
use-package
itself to be available during byte compilation. The standard magic way to do this is:
(eval-when-compile (require 'use-package))
However, there's a complication;
marginalia--fields
is a macro and the syntax it takes is different enough to trigger a byte compilation error if it's interpreted as a function instead. If the macro was defined at byte compilation time, there would be no problem, but even with use-package available, the macro isn't pulled in because of (the lack of) package initialization . Marginalia is a third party package that has its Lisp files in an ever-changing subdirectory in
~/.emacs.d/elpa/
, and that's not on the Emacs
load-path
until package initialization happens, so although use-package is active it can't find and load Marginalia to get the macro defined.
So, the clever person thinks, much like we force use-package to be pulled in during byte compilation, we can also force package initialization:
(eval-when-compile (package-initialize))
If I put this in the ELisp file with the above code, now the code passes byte compilation checks. However, if I start Emacs regularly, I get a warning:
Warning (package): Unnecessary call to 'package-initialize' in init file
This is happening because I also have a
package-initialize
in my .emacs file ( somewhat for historical reasons ). When my Emacs starts, both .emacs and this file are loaded, both run their
package-initialize
, and I get the complaint. When I made my Fediverse post , I was imagining a version of '
eval-when-compile
' that silently didn't do anything when interpreted, so the guarded
package-initialize
would only run during byte compile checks, not during Emacs startup.
(I'm already not byte compiling these files normally, but obviously this is a bit dangerous. Which is sort of a hint that this was the wrong approach.)
Flycheck's emacs-lisp checker can automatically initialize packages at the start of its byte compilation check, but it normally does this only for files in
user-emacs-directory
(normally
~/.emacs.d
), and I don't put my startup files there for various reasons. Setting
flycheck-emacs-lisp-initialize-packages
to 't' makes Flycheck do it all the time. I don't know if forcing Flycheck to initialize packages all the time may have side effects if you wind up working on Emacs Lisp things that aren't part of your startup files. This isn't an issue for me since I don't write Emacs Lisp outside of that sort of stuff.
Sidebar: Fixing this the more clever way
Flycheck has a function to decide whether or not a file is under your user Emacs directory,
flycheck-in-user-emacs-directory-p
. The morally correct way to fix my issue would be to use
advice-add
to also have it return true for files in the relevant bit of my personal Emacs Lisp directory tree, and to leave
flycheck-emacs-lisp-initialize-packages
at its default 'auto' setting.
(I'm probably not energetic enough to write the code necessary. It's possible that
flycheck-emacs-lisp-package-user-dir
is what I want to change, but I'm not clear what the effects of that are and I'm wary of tampering with it.)