Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Byte compilation warning "the function ‘js2-imenu-extras-setup’ might not be defined at runtime." #1032

Open
YorkZ opened this issue Dec 23, 2022 · 8 comments · May be fixed by #1059
Open

Comments

@YorkZ
Copy link

YorkZ commented Dec 23, 2022

This simple code:

(use-package js2-imenu-extras
  :functions js2-imenu-extras-setup
  :config
  (js2-imenu-extras-setup))

gives me the byte compilation warning:

the function ‘js2-imenu-extras-setup’ might not be defined at runtime.

I've spent hours but still wasn't able to silence the warning. I'm wondering whether it would be possible to solve it.

@redblobgames
Copy link

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:

@AlynxZhou
Copy link

I can reproduce this, too, using :commands instead of :functions works, but they have different meanings.

@AlynxZhou
Copy link

AlynxZhou commented Jan 20, 2023

I see @skangas asking for a minimal reproduce step for this in #636 (comment), 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:

  1. Install flycheck, rainbow-mode (or other package, I use this as example) and hook flycheck-mode with emacs-lisp-mode-hook so it will run.
  2. Create a file called 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"))
  1. M-x flycheck-compile RET emacs-lisp RET

You 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:

  1. M-: (setq byte-compile-current-file (current-buffer)) RET
  2. M-x pp-macroexpand-last-sexp RET

Then 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!

@YorkZ
Copy link
Author

YorkZ commented Jan 20, 2023

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?

@AlynxZhou
Copy link

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?

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.

@jyp
Copy link

jyp commented Jul 7, 2023

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.

@jyp
Copy link

jyp commented Jul 10, 2023

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.

@jyp
Copy link

jyp commented Jul 10, 2023

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.

jyp added a commit to jyp/use-package that referenced this issue Jul 19, 2023
With this change, `use-package p` puts in scope all the symbols
declared by package p in scope of its :config block. The tradeoff is
that init is slightly slower *if interpreted*. Compiling init.el gives
the same runtime behaviour as previously.

Fixes jwiegley#1032
@jyp jyp linked a pull request Jul 20, 2023 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants