emacs-evil / evil

The extensible vi layer for Emacs.
GNU General Public License v3.0
3.38k stars 281 forks source link

Unable to set certain variables by `:custom` in `use-package` #1836

Open peromage opened 1 year ago

peromage commented 1 year ago

Issue type

Environment

Emacs version: GNU Emacs 29.1 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.38, cairo version 1.17.8) Operating System: Arch Linux Evil version: Evil version 1.15.0 Evil installation type: MELPA Graphical/Terminal: Wayland/Terminal Tested in a make emacs session (see CONTRIBUTING.md): No

Reproduction steps

  1. Set variable evil-want-Y-yank-to-eol with use-package

    (pewcfg::use-package evil
    :custom
    (evil-want-Y-yank-to-eol t)
    :config
    (evil-mode 1))
  2. Start Emacs

Expected behavior

The value of evil-want-Y-yank-to-eol is t.

Actual behavior

The value of evil-want-Y-yank-to-eol is nil.

Further notes

This seems to be a bug caused by the initializers stored in evil-pending-custom-initialize.

For example, the custom variable evil-want-Y-yank-to-eol has a initializer evil-custom-initialize-pending-reset which adds a initialization form to the list evil-pending-custom-initialize. https://github.com/emacs-evil/evil/blob/d28206ccff74bc07ba335b8ff77805564f6928d7/evil-vars.el#L623-L628

That list is iterated by evil-run-pending-custom-initialize. https://github.com/emacs-evil/evil/blob/d28206ccff74bc07ba335b8ff77805564f6928d7/evil-vars.el#L68-L74

That function is called by hook evil-after-load-hook whenever package evil is required. https://github.com/emacs-evil/evil/blob/d28206ccff74bc07ba335b8ff77805564f6928d7/evil.el#L154

If we expand a use-package form

(message "%s" (pp-to-string (macroexpand-all '(use-package evil 
                                                :init
                                                (something in :init)
                                                :config
                                                (something else in :config)
                                                (evil-mode 1)
                                                :custom
                                                (evil-want-Y-yank-to-eol t)))))

Then we have

(progn
  (use-package-ensure-elpa 'evil
                           '(t)
                           'nil)
  (defvar use-package--warning71
    #'(lambda
        (keyword err)
        (let
            ((msg
              (format "%s/%s: %s" 'evil keyword
                      (error-message-string err))))
          (display-warning 'use-package msg :error))))
  (condition-case err
      (progn
        (let
            ((custom--inhibit-theme-enable nil))
          (if
              (memq 'use-package custom-known-themes)
              nil
            (custom-declare-theme 'use-package 'use-package-theme nil
                                  (list))
            (enable-theme 'use-package)
            (setq custom-enabled-themes
                  (remq 'use-package custom-enabled-themes)))
          (custom-theme-set-variables 'use-package
                                      '(evil-want-Y-yank-to-eol t nil nil "Customized with use-package evil")))
        (condition-case err
            (something in :init)
          ((debug error)
           (funcall use-package--warning71 :init err)))
        (if
            (not
             (require 'evil nil t))
            (display-warning 'use-package
                             (format "Cannot load %s" 'evil)
                             :error)
          (condition-case err
              (progn
                (something else in :config)
                (evil-mode 1)
                t)
            ((debug error)
             (funcall use-package--warning71 :config err)))))
    ((debug error)
     (funcall use-package--warning71 :catch err))))

We can see that custom-theme-set-variables is invoked before require. Then our customized value is inevitably overwritten.

Same thing applies to variables

However, a strange thing is, if these variable are set with setq in either :init or :config block of use-package, they work perfectly fine. I think this might because of the setter of these custom variables, which uses set-default. I don't know too much about this mechanism.

Final thought

evil-pending-custom-initialize was added 11 years ago. https://github.com/emacs-evil/evil/commit/b26b2861d943a601814ac373c8deb7e4863fbaec

This piece of code seems stale and not update-to-date with current use cases. I can see a lot custom variables now in Evil do not use this initialization idiom.

Do we still need it?

peromage commented 1 year ago

Someone asked the same problem back in 2021 but the issue was closed as it was not considered an Evil bug: https://github.com/emacs-evil/evil/issues/1486#issuecomment-876371225