Open YorkZ opened 1 year ago
I always have trouble making :functions
work for me. Try M-x pp-macroexpand-expression
and you'll see your :functions
doesn't seem to show up anywhere.
Other people have issues too, and you might try some of the suggestions in these issues:
I can reproduce this, too, using :commands
instead of :functions
works, but they have different meanings.
I see @skangas asking for a minimal reproduce step for this in https://github.com/jwiegley/use-package/issues/636#issuecomment-1342022704, I do not byte-compile my init file, but I got those warnings from flycheck, and I'd like to show how to reproduce it:
test.el
with following content and save it under your ~/.emacs.d/
(!!!IMPORTANT!!!):(use-package rainbow-mode
:functions (rainbow-x-color-luminance)
:config
(rainbow-x-color-luminance "cyan"))
flycheck-compile
RET emacs-lisp
RETYou should get a warning about the function ‘rainbow-x-color-luminance’ might not be defined at runtime.
, and it's not expected, I already define it with :functions
.
I also did further investigetion, first this only happens when a file is under ~/.emacs.d/
, otherwise you just get an error about Cannot load rainbow-mode
and it will skip this code block.
I also tried to expand the macro, but in compiling state with the following hack:
(setq byte-compile-current-file (current-buffer))
RETpp-macroexpand-last-sexp
RETThen I got the following result:
(progn
(eval-and-compile
(declare-function rainbow-x-color-luminance "rainbow-mode")
(eval-when-compile
(with-demoted-errors "Cannot load rainbow-mode: %S" nil
(unless
(featurep 'rainbow-mode)
(load "rainbow-mode" nil t)))))
(defvar use-package--warning67
#'(lambda
(keyword err)
(let
((msg
(format "%s/%s: %s" 'rainbow-mode keyword
(error-message-string err))))
(display-warning 'use-package msg :error))))
(condition-case-unless-debug err
(if
(not
(require 'rainbow-mode nil t))
(display-warning 'use-package
(format "Cannot load %s" 'rainbow-mode)
:error)
(condition-case-unless-debug err
(progn
(rainbow-x-color-luminance "cyan")
t)
(error
(funcall use-package--warning67 :config err))))
(error
(funcall use-package--warning67 :catch err))))
So use-package
do declare functions in compiling. But if I save those results in another file under ~/.emacs.d/
, and do M-x flycheck-compile
RET emacs-lisp
RET, I got the same warning (the function ‘rainbow-x-color-luminance’ might not be defined at runtime.
).
So the problem is: we declared the function, but seems not help.
Hoping those infomation helpful to you!
Thanks @AlynxZhou for sharing. Your trick of setting byte-compile-current-file
to (current-buffer)
before expanding the macro is really interesting. I never knew this before, and each time I expanded the use-package
macro that specified :functions FUNCTION
, the expanded code never has the declaration of the FUNCTION
. Can you explain the mechanism behind this?
Thanks @AlynxZhou for sharing. Your trick of setting
byte-compile-current-file
to(current-buffer)
before expanding the macro is really interesting. I never knew this before, and each time I expanded theuse-package
macro that specified:functions FUNCTION
, the expanded code never has the declaration of theFUNCTION
. Can you explain the mechanism behind this?
See https://github.com/jwiegley/use-package/blob/master/use-package-core.el#L658-L674.
Those keywords only exist to make byte-compiler happy, so use-package
only expands them when byte-compiling, if you expand it normally you won't get them. And the actual problem is use-package
do declare functions but byte-compiler still gives warnings.
The use-package documentation states:
Normally, use-package will load each package at compile time before compiling the configuration, to ensure that any necessary symbols are in scope to satisfy the byte-compiler.
It appears that this is false in general and there is an error in use-package
. Indeed, consider for example:
(use-package js2-imenu-extras
:defer t
:config
(js2-imenu-extras-setup))
This expands to:
(progn
(eval-and-compile
(eval-when-compile
(with-demoted-errors "Cannot load js2-imenu-extras: %S" nil
(unless
(featurep 'js2-imenu-extras)
(load "js2-imenu-extras" nil t)))))
(eval-after-load 'js2-imenu-extras
'(progn
(js2-imenu-extras-setup)
t)))
The call (load "js2-imenu-extras" nil t)
is indeed there, but it is in the body of eval-when-compile
. As far as I understand, this does not cause the byte-compiler to load the required file. For this to work, eval-and-compile
should be used. (Don't ask me why). So, the fix appears to be to replace, in use-package-core.el, after line 666 (yes really):
`((eval-when-compile
(with-demoted-errors
by
`((eval-and-compile
(with-demoted-errors
Please confirm that this fixes the problem for you, and perhaps submit a PR.
Unfortunately my suggestion causes some runtime cost--- so it's not suitable. For reference, with eval-when-compile
my init time is abound 0.35 seconds, but it goes to 0.70 seconds with eval-and-compile
. This is when init.el is interpreted. If I try to compile it, the init time goes to 2.7 seconds.
I've kept researching the issue, and I found that this variant is better:
(defmacro jyp/eval-when-compile (&rest body)
"Evaluate BODY at compile time.
When expanding the macro, evaluate (progn BODY) and substitute
the macro call by the result of the evaluation. If BODY
is (require feature), then all the symbols declared in feature
are visible to the compiler. This contrasts with
`eval-when-compile', which only puts *variables* in scope."
(list 'quote (eval (cons 'progn body))))
Using it, init time is 0.7s when init.el is interpreted, and 0.35s when it is compiled. I'm not sure what kind of magic is performed by eval-when-compile
. I cannot explain the timings that I'm seeing either.
This simple code:
gives me the byte compilation warning:
I've spent hours but still wasn't able to silence the warning. I'm wondering whether it would be possible to solve it.