Changes to the way auto-deferral is indicated

This change adds a new extension hook `use-package-autoloads/<KEYWORD>` for
specifying exactly which autoloads a keyword should imply. This is the proper
way to indicate autoloads, rather than adding to the `:commands` entry as was
done before.

Further, autoloading now must occur in order to cause implied deferred
loading; if :bind is used with only lambda forms, for example, this will not
cause deferred loading without `:defer t`.
This commit is contained in:
John Wiegley 2017-12-07 13:14:32 -08:00
parent 80e8a599b4
commit 8fefa49d39
4 changed files with 181 additions and 109 deletions

View file

@ -30,10 +30,6 @@
parameter, but are now done as an extension to `rest`. Please see
`use-package-handler/:bind` for a canonical example.
- For extension authors, if you add a keyword to `use-package-keywords` whose
presence should indicate deferred loading, please also add it to
`use-package-deferring-keywords`.
### Other changes
- Upgrade license to GPL 3.
@ -154,6 +150,28 @@
it is loaded, `helm-descbinds` itself is not loaded until the user presses
`C-h b`.
- For extension authors, if you add a keyword to `use-package-keywords` whose
presence should indicate deferred loading, please also add it to
`use-package-deferring-keywords`. Note that this is a bit of a sledgehammer,
in that the mere presence of these keywords implies deferred loading. For a
more subtle approach, see the new `use-package-autoloads/<KEYWORD>` support
mentioned in the next bullet.
- For extension authors, if you wish deferred loading to possibly occur,
create functions named `use-package-autoloads/<KEYWORD>` for each keyword
that you define, returning an alist of the form `(SYMBOL . TYPE)` of symbols
to be autoloaded. `SYMBOL` should be an interactive function, and `TYPE` the
smybol `command`, but this functionality may be extended in future. These
autoloads are established if deferred loading is to happen.
- If you specify a lambda form rather than a function symbol in any of the
constructs that *might* introduce autoloads: `:bind`, `:bind*`,
`:interpreter`, `:mode`, `:magic`, `:magic-fallback`, and `:hook`: then
deferred loading will no longer be implied, since there's nothing to
associate an autoload with that could later load the module. In these cases,
it will be as if you'd specified `:demand t`, in order to ensure the lambda
form is able to execute in the context of the loaded package.
- For extension authors, there is a new customization variable
`use-package-merge-key-alist` that specifies how values passed to multiple
occurences of the same key should be merged into a single value, during

View file

@ -114,20 +114,23 @@ deferred until the prefix key sequence is pressed."
;;;###autoload
(defalias 'use-package-normalize/:bind* 'use-package-normalize-binder)
;; jww (2017-12-07): This is too simplistic. It will fail to determine
;; autoloads in this situation:
;; (use-package foo
;; :bind (:map foo-map (("C-a" . func))))
;;;###autoload
(defalias 'use-package-autoloads/:bind 'use-package-autoloads-mode)
;;;###autoload
(defalias 'use-package-autoloads/:bind* 'use-package-autoloads-mode)
;;;###autoload
(defun use-package-handler/:bind
(name keyword args rest state &optional bind-macro)
(let* ((result (use-package-normalize-commands args))
(nargs (car result))
(commands (cdr result)))
(use-package-concat
(use-package-process-keywords name
(use-package-sort-keywords
(use-package-plist-append rest :commands commands))
state)
`((ignore
(,(if bind-macro bind-macro 'bind-keys)
:package ,name ,@nargs))))))
(use-package-concat
(use-package-process-keywords name rest state)
`((ignore
(,(if bind-macro bind-macro 'bind-keys)
:package ,name ,@(use-package-normalize-commands args))))))
(defun use-package-handler/:bind* (name keyword arg rest state)
(use-package-handler/:bind name keyword arg rest state 'bind-keys*))

View file

@ -100,17 +100,15 @@ declaration is incorrect."
:group 'use-package)
(defcustom use-package-deferring-keywords
'(:bind
:bind*
:bind-keymap
'(:bind-keymap
:bind-keymap*
:interpreter
:mode
:magic
:magic-fallback
:hook
:commands)
"Unless `:demand' is used, keywords in this list imply deferred loading."
"Unless `:demand' is used, keywords in this list imply deferred loading.
The reason keywords like `:hook' are not in this list is that
they only imply deferred loading if they reference actual
function symbols that can be autoloaded from the module; whereas
the default keywords provided here always defer loading unless
otherwise requested."
:type '(repeat symbol)
:group 'use-package)
@ -160,7 +158,7 @@ See also `use-package-defaults', which uses this value."
(and use-package-always-demand
(not (plist-member args :defer))
(not (plist-member args :demand))))))
"Alist of default values for `use-package' keywords.
"Default values for specified `use-package' keywords.
Each entry in the alist is a list of three elements. The first
element is the `use-package' keyword and the second is a form
that can be evaluated to get the default value. The third element
@ -497,8 +495,9 @@ extending any keys already present."
(xs (use-package-split-list #'keywordp (cdr input)))
(args (car xs))
(tail (cdr xs))
(normalizer (intern (concat "use-package-normalize/"
(symbol-name keyword))))
(normalizer
(intern-soft (concat "use-package-normalize/"
(symbol-name keyword))))
(arg (and (functionp normalizer)
(funcall normalizer name keyword args))))
(if (memq keyword use-package-keywords)
@ -562,6 +561,32 @@ extending any keys already present."
(setq args (use-package-plist-maybe-put
args (nth 0 spec) (eval (nth 1 spec))))))
;; Determine any autoloads implied by the keywords used.
(let ((iargs args)
commands)
(while iargs
(when (keywordp (car iargs))
(let ((autoloads
(intern-soft (concat "use-package-autoloads/"
(symbol-name (car iargs))))))
(when (functionp autoloads)
(setq commands
;; jww (2017-12-07): Right now we just ignored the type of
;; the autoload being requested, and assume they are all
;; `command'.
(append (mapcar
#'car
(funcall autoloads name-symbol (car iargs)
(cadr iargs)))
commands)))))
(setq iargs (cddr iargs)))
(when commands
(setq args
;; Like `use-package-plist-append', but removing duplicates.
(plist-put args :commands
(delete-dups
(append commands (plist-get args :commands)))))))
;; If byte-compiling, pre-load the package so all its symbols are in
;; scope. This is done by prepending statements to the :preface.
(when (bound-and-true-p byte-compile-current-file)
@ -593,10 +618,13 @@ extending any keys already present."
use-package-deferring-keywords)))
(setq args (append args '(:defer t))))
;; The :load keyword overrides :no-require
(when (and (plist-member args :load)
(plist-member args :no-require))
(setq args (use-package-plist-delete args :no-require)))
;; If at this point no :load, :defer or :no-require has been seen, then
;; :load the package itself.
(when (and (not (plist-member args :load))
(not (plist-member args :defer))
(not (plist-member args :no-require)))
@ -851,22 +879,12 @@ If RECURSED is non-nil, recurse into sublists."
(t v)))
(defun use-package-normalize-commands (args)
"Map over ARGS of the form ((_ . F) ...).
Normalizing functional F's and returning a list of F's
representing symbols (that may need to be autloaded)."
(let ((nargs (mapcar
#'(lambda (x)
(if (consp x)
(cons (car x)
(use-package-normalize-function (cdr x)))
x)) args)))
(cons nargs
(delete
nil (mapcar
#'(lambda (x)
(and (consp x)
(use-package-non-nil-symbolp (cdr x))
(cdr x))) nargs)))))
"Map over ARGS of the form ((_ . F) ...), normalizing functional F's."
(mapcar #'(lambda (x)
(if (consp x)
(cons (car x) (use-package-normalize-function (cdr x)))
x))
args))
(defun use-package-normalize-mode (name keyword args)
"Normalize arguments for keywords which add regexp/mode pairs to an alist."
@ -876,6 +894,27 @@ representing symbols (that may need to be autloaded)."
#'use-package-recognize-function
name)))
(defun use-package-autoloads-mode (name keyword args)
(mapcar
#'(lambda (x) (cons (cdr x) 'command))
(cl-remove-if-not #'(lambda (x)
(and (consp x)
(use-package-non-nil-symbolp (cdr x))))
args)))
(defun use-package-handle-mode (name alist args rest state)
"Handle keywords which add regexp/mode pairs to an alist."
(use-package-concat
(use-package-process-keywords name rest state)
`((ignore
,@(mapcar
#'(lambda (thing)
`(add-to-list
',alist
',(cons (use-package-normalize-regex (car thing))
(cdr thing))))
(use-package-normalize-commands args))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Statistics
@ -1078,26 +1117,8 @@ meaning:
;;;; :interpreter
(defun use-package-handle-mode (name alist args rest state)
"Handle keywords which add regexp/mode pairs to an alist."
(let* ((result (use-package-normalize-commands args))
(nargs (car result))
(commands (cdr result)))
(use-package-concat
(use-package-process-keywords name
(use-package-sort-keywords
(use-package-plist-append rest :commands commands))
state)
`((ignore
,@(mapcar
#'(lambda (thing)
`(add-to-list
',alist
',(cons (use-package-normalize-regex (car thing))
(cdr thing))))
nargs))))))
(defalias 'use-package-normalize/:interpreter 'use-package-normalize-mode)
(defalias 'use-package-autoloads/:interpreter 'use-package-autoloads-mode)
(defun use-package-handler/:interpreter (name keyword arg rest state)
(use-package-handle-mode name 'interpreter-mode-alist arg rest state))
@ -1105,6 +1126,7 @@ meaning:
;;;; :mode
(defalias 'use-package-normalize/:mode 'use-package-normalize-mode)
(defalias 'use-package-autoloads/:mode 'use-package-autoloads-mode)
(defun use-package-handler/:mode (name keyword arg rest state)
(use-package-handle-mode name 'auto-mode-alist arg rest state))
@ -1112,6 +1134,7 @@ meaning:
;;;; :magic
(defalias 'use-package-normalize/:magic 'use-package-normalize-mode)
(defalias 'use-package-autoloads/:magic 'use-package-autoloads-mode)
(defun use-package-handler/:magic (name keyword arg rest state)
(use-package-handle-mode name 'magic-mode-alist arg rest state))
@ -1119,6 +1142,7 @@ meaning:
;;;; :magic-fallback
(defalias 'use-package-normalize/:magic-fallback 'use-package-normalize-mode)
(defalias 'use-package-autoloads/:magic-fallback 'use-package-autoloads-mode)
(defun use-package-handler/:magic-fallback (name keyword arg rest state)
(use-package-handle-mode name 'magic-fallback-mode-alist arg rest state))
@ -1145,31 +1169,27 @@ meaning:
#'use-package-recognize-function
name label arg))))
(defalias 'use-package-autoloads/:hook 'use-package-autoloads-mode)
(defun use-package-handler/:hook (name keyword args rest state)
"Generate use-package custom keyword code."
(let* ((result (use-package-normalize-commands args))
(nargs (car result))
(commands (cdr result)))
(use-package-concat
(use-package-process-keywords name
(use-package-sort-keywords
(use-package-plist-append rest :commands commands))
state)
`((ignore
,@(cl-mapcan
#'(lambda (def)
(let ((syms (car def))
(fun (cdr def)))
(when fun
(mapcar
#'(lambda (sym)
`(add-hook
(quote ,(intern
(concat (symbol-name sym)
use-package-hook-name-suffix)))
(function ,fun)))
(if (use-package-non-nil-symbolp syms) (list syms) syms)))))
nargs))))))
(use-package-concat
(use-package-process-keywords name rest state)
`((ignore
,@(cl-mapcan
#'(lambda (def)
(let ((syms (car def))
(fun (cdr def)))
(when fun
(mapcar
#'(lambda (sym)
`(add-hook
(quote ,(intern
(concat (symbol-name sym)
use-package-hook-name-suffix)))
(function ,fun)))
(if (use-package-non-nil-symbolp syms) (list syms) syms)))))
(use-package-normalize-commands args))))))
;;;; :commands

View file

@ -1001,30 +1001,30 @@
(ert-deftest use-package-test/:hook-1 ()
(let ((byte-compile-current-file t))
(should
(equal
(expand-minimally
(use-package foo
:bind (("C-a" . key))
:hook (hook . fun)))
'(progn
(eval-and-compile
(eval-when-compile
(with-demoted-errors
"Cannot load foo: %S" nil
(load "foo" nil t))))
(unless (fboundp 'fun)
(autoload #'fun "foo" nil t))
(eval-when-compile
(declare-function fun "foo"))
(unless (fboundp 'key)
(autoload #'key "foo" nil t))
(eval-when-compile
(declare-function key "foo"))
(ignore
(add-hook 'hook-hook #'fun))
(ignore
(bind-keys :package foo ("C-a" . key))))))))
(match-expansion
(use-package foo
:bind (("C-a" . key))
:hook (hook . fun))
`(progn
(eval-and-compile
(eval-when-compile
(with-demoted-errors
"Cannot load foo: %S" nil
(load "foo" nil t))))
(unless
(fboundp 'key)
(autoload #'key "foo" nil t))
(eval-when-compile
(declare-function key "foo"))
(unless
(fboundp 'fun)
(autoload #'fun "foo" nil t))
(eval-when-compile
(declare-function fun "foo"))
(ignore
(add-hook 'hook-hook #'fun))
(ignore
(bind-keys :package foo ("C-a" . key)))))))
(ert-deftest use-package-test/:hook-2 ()
(match-expansion
@ -1079,6 +1079,37 @@
#'(lambda nil
(bind-key "" erefactor-map emacs-lisp-mode-map)))))))))
(ert-deftest use-package-test/:hook-6 ()
(match-expansion
(use-package erefactor
:load-path "foo"
:after elisp-mode
:hook (emacs-lisp-mode . function))
`(progn
(eval-and-compile
(add-to-list 'load-path "/Users/johnw/.emacs.d/foo"))
(eval-after-load 'elisp-mode
'(progn
(unless (fboundp 'function)
(autoload #'function "erefactor" nil t))
(ignore
(add-hook 'emacs-lisp-mode-hook #'function)))))))
(ert-deftest use-package-test/:hook-7 ()
(match-expansion
(use-package erefactor
:load-path "foo"
:after elisp-mode
:hook (emacs-lisp-mode . (lambda () (function))))
`(progn
(eval-and-compile
(add-to-list 'load-path "/Users/johnw/.emacs.d/foo"))
(eval-after-load 'elisp-mode
'(progn
(require 'erefactor nil nil)
(ignore
(add-hook 'emacs-lisp-mode-hook #'(lambda nil (function)))))))))
(ert-deftest use-package-test-normalize/:custom ()
(flet ((norm (&rest args)
(apply #'use-package-normalize/:custom