Refactor after! macro
The trouble with with-eval-after-load is it arranges for the body to be byte-compiled, whereas eval-after-load does not. I won't go into how they do that here, but it causes us some trouble: Macro calls in with-eval-after-load are eagerly (immediately) expanded at startup, whether or not the package or macro is available and regardless of its execution path. This sucks for Doom because, when expanded, autoloaded macros will be loaded, along with whatever baggage they've got with them, and this happens long before they're actually used. We also can't guarantee those macros are available at startup, which will cause void-function errors when the interpreter later treats them like an ordinary function call. So, the simple fix is to pass a quoted body form to eval-after-load instead of the closure that with-eval-after-load will wrap it in. This means the body won't get byte-compiled if we compile our config, but in exchange, macros stay lazy-loaded until they're finally needed! Wonderful.
This commit is contained in:
parent
ffadd1307d
commit
0c678ee762
1 changed files with 36 additions and 36 deletions
|
@ -507,54 +507,54 @@ CATEGORY and MODULE can be omitted When this macro is used from inside a module
|
|||
t))
|
||||
|
||||
(defmacro after! (targets &rest body)
|
||||
"Evaluate BODY after TARGETS (packages) have loaded.
|
||||
"Evaluate BODY after TARGETS have loaded.
|
||||
|
||||
This is a wrapper around `with-eval-after-load' that:
|
||||
|
||||
1. Suppresses warnings for disabled packages at compile-time
|
||||
2. No-ops for TARGETS that are disabled by the user (via `package!')
|
||||
3. Supports compound TARGETS statements (see below)
|
||||
|
||||
TARGETS is a list of packages in one of these formats:
|
||||
TARGETS is a symbol or list of them. These are package names, not modes,
|
||||
functions or variables. It can be:
|
||||
|
||||
- An unquoted package symbol (the name of a package)
|
||||
(after! helm BODY...)
|
||||
- An unquoted list of package symbols (i.e. BODY is evaluated once both magit
|
||||
and git-gutter have loaded)
|
||||
(after! (magit git-gutter) BODY...)
|
||||
- An unquoted, nested list of compound package lists, using :or/:any and/or
|
||||
:and/:all
|
||||
- An unquoted, nested list of compound package lists, using any combination of
|
||||
:or/:any and :and/:all
|
||||
(after! (:or package-a package-b ...) BODY...)
|
||||
(after! (:and package-a package-b ...) BODY...)
|
||||
(after! (:and package-a (:or package-b package-c) ...) BODY...)
|
||||
Without :or/:any/:and/:all, :and/:all are implied.
|
||||
|
||||
Note that:
|
||||
- :or and :any are equivalent
|
||||
- :and and :all are equivalent
|
||||
- If these are omitted, :and is implied."
|
||||
This is a wrapper around `eval-after-load' that:
|
||||
|
||||
1. Suppresses warnings for disabled packages at compile-time
|
||||
2. No-ops for TARGETS that are disabled by the user (via `package!')
|
||||
3. Supports compound TARGETS statements (see below)
|
||||
4. Prevents eager expansion pulling in autoloaded macros all at once"
|
||||
(declare (indent defun) (debug t))
|
||||
(unless (and (symbolp targets)
|
||||
(memq targets (bound-and-true-p doom-disabled-packages)))
|
||||
(list (if (or (not (bound-and-true-p byte-compile-current-file))
|
||||
(dolist (next (doom-enlist targets))
|
||||
(unless (keywordp next)
|
||||
(if (symbolp next)
|
||||
(require next nil :no-error)
|
||||
(load next :no-message :no-error)))))
|
||||
#'progn
|
||||
#'with-no-warnings)
|
||||
(if (symbolp targets)
|
||||
`(with-eval-after-load ',targets ,@body)
|
||||
(pcase (car-safe targets)
|
||||
((or :or :any)
|
||||
(macroexp-progn
|
||||
(cl-loop for next in (cdr targets)
|
||||
collect `(after! ,next ,@body))))
|
||||
((or :and :all)
|
||||
(dolist (next (cdr targets))
|
||||
(setq body `((after! ,next ,@body))))
|
||||
(car body))
|
||||
(_ `(after! (:and ,@targets) ,@body)))))))
|
||||
(if (symbolp targets)
|
||||
(unless (memq targets (bound-and-true-p doom-disabled-packages))
|
||||
(list (if (or (not (bound-and-true-p byte-compile-current-file))
|
||||
(require next nil 'noerror))
|
||||
#'progn
|
||||
#'with-no-warnings)
|
||||
(let ((body (macroexp-progn body)))
|
||||
`(if (featurep ',targets)
|
||||
,body
|
||||
;; We intentionally avoid `with-eval-after-load' to prevent
|
||||
;; eager macro expansion from pulling (or failing to pull) in
|
||||
;; autoloaded macros/packages.
|
||||
(eval-after-load ',targets ',body)))))
|
||||
(let ((target (car-safe targets)))
|
||||
(cond ((not (keywordp target))
|
||||
`(after! (:and ,@targets) ,@body))
|
||||
((memq target '(:or :any))
|
||||
(macroexp-progn
|
||||
(cl-loop for next in (cdr targets)
|
||||
collect `(after! ,next ,@body))))
|
||||
((memq target '(:and :all))
|
||||
(dolist (next (cdr targets))
|
||||
(setq body `((after! ,next ,@body))))
|
||||
(car body))))))
|
||||
|
||||
(provide 'core-modules)
|
||||
;;; core-modules.el ends here
|
||||
|
|
Loading…
Reference in a new issue