1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-06 06:20:55 -08:00

Implement new autoload macro expansion declare form

Currently, a hard-coded set of macros is automatically expanded
during generation of autoloads.  To allow user macros to request
such expansion, this implements a new declare form
`autoload-macro' (Bug#78995), with supported value `expand'.
For example, macros which wrap `define-minor-mode', can declare
`(autoload-macro expand)' to request that ;;;###autoload-adorned
calls to the macro are expanded during generation, such that an
autoload for the resulting function is created.

* lisp/emacs-lisp/byte-run.el (byte-run--set-autoload-macro):
Handle autoload-macro declare forms.
(macro-declarations-alist) Add handler for 'autoload-macro
declare forms.
(defmacro, defun):
* lisp/emacs-lisp/cl-generic.el (cl-defgeneric, cl-defun)
(cl-iter-defun, cl-defmacro, cl-defstruct):
* lisp/emacs-lisp/easy-mmode.el
(define-minor-mode, define-globalized-minor-mode, iter-defun):
* lisp/emacs-lisp/inline.el (define-inline):
* lisp/emacs-lisp/pcase.el (pcase-defmacro):
Declare (autoload-macro expand) to request expansion of the
macro during autoload generation.

* lisp/emacs-lisp/loaddefs-gen.el (loaddefs-generate--make-autoload):
Handle the `autoload-macro=expand' property for macros.  Load
the ;;;###autoload-containing file if an unknown symbol is
encountered in the car of the following form, to give packages a
chance to define their macros and request expansion.  Factor
list of special function-defining macros out as a constant
variable: `loaddefs--defining-macros'.

* doc/lispref/functions.texi (Declare Form):
* doc/lispref/loading.texi (Autoload): Document `autoload-macro'.
This commit is contained in:
JD Smith 2025-07-24 15:42:10 -04:00
parent 3d7f51d872
commit 7486e5c368
10 changed files with 164 additions and 56 deletions

View file

@ -286,6 +286,12 @@ This is used by `declare'.")
(list 'put (list 'quote name)
''edebug-form-spec (list 'quote spec)))))
(defalias 'byte-run--set-autoload-macro
#'(lambda (name _args spec)
(list 'function-put (list 'quote name)
''autoload-macro (list 'quote spec)))
"Handle autoload-macro declarations")
(defalias 'byte-run--set-no-font-lock-keyword
#'(lambda (name _args val)
(list 'function-put (list 'quote name)
@ -365,8 +371,13 @@ This is used by `declare'.")
(cons
(list 'debug #'byte-run--set-debug)
(cons
(list 'no-font-lock-keyword #'byte-run--set-no-font-lock-keyword)
defun-declarations-alist))
;; macros can declare (autoload-macro expand) to request expansion
;; during autoload generation of forms calling them. See
;; `loaddefs-generate--make-autoload'.
(list 'autoload-macro #'byte-run--set-autoload-macro)
(cons
(list 'no-font-lock-keyword #'byte-run--set-no-font-lock-keyword)
defun-declarations-alist)))
"List associating properties of macros to their macro expansion.
Each element of the list takes the form (PROP FUN) where FUN is a function.
For each (PROP . VALUES) in a macro's declaration, the FUN corresponding
@ -412,6 +423,8 @@ The return value is undefined.
(if declarations
(cons 'prog1 (cons def (car declarations)))
def))))))
;; Expand to defalias and related forms on autoload gen
(function-put 'defmacro 'autoload-macro 'expand) ; Since we cannot `declare' it
;; Now that we defined defmacro we can use it!
(defmacro defun (name arglist &rest body)
@ -424,7 +437,9 @@ INTERACTIVE is an optional `interactive' specification.
The return value is undefined.
\(fn NAME ARGLIST [DOCSTRING] [DECL] [INTERACTIVE] BODY...)"
(declare (doc-string 3) (indent 2))
(declare (doc-string 3) (indent 2)
;; Expand to defalias on autoload gen
(autoload-macro expand))
(or name (error "Cannot define '%s' as a function" name))
(if (null
(and (listp arglist)

View file

@ -258,7 +258,9 @@ DEFAULT-BODY, if present, is used as the body of a default method.
cl--generic-edebug-make-name in:method]
lambda-doc
def-body)]]
def-body)))
def-body))
;; Expand to defun and related forms on autoload definition
(autoload-macro expand))
(let* ((doc (if (stringp (car-safe options-and-methods))
(pop options-and-methods)))
(declarations nil)

View file

@ -396,7 +396,9 @@ more details.
[&optional ("interactive" interactive)]
def-body))
(doc-string 3)
(indent 2))
(indent 2)
;; expand to function definition on autoload gen
(autoload-macro expand))
`(defun ,name ,@(cl--transform-lambda (cons args body) name)))
;;;###autoload
@ -414,7 +416,9 @@ and BODY is implicitly surrounded by (cl-block NAME ...).
[&optional ("interactive" interactive)]
def-body))
(doc-string 3)
(indent 2))
(indent 2)
;; expand (eventually) to function definition on autoload gen
(autoload-macro expand))
(require 'generator)
`(iter-defun ,name ,@(cl--transform-lambda (cons args body) name)))
@ -473,7 +477,8 @@ more details.
(declare (debug
(&define name cl-macro-list cl-declarations-or-string def-body))
(doc-string 3)
(indent 2))
(indent 2)
(autoload-macro expand)) ; expand to defmacro on autoload gen
`(defmacro ,name ,@(cl--transform-lambda (cons args body) name)))
(def-edebug-elem-spec 'cl-lambda-expr
@ -3087,7 +3092,9 @@ To see the documentation for a defined struct type, use
sexp])]
[&optional stringp]
;; All the above is for the following def-form.
&rest &or symbolp (symbolp &optional def-form &rest sexp))))
&rest &or symbolp (symbolp &optional def-form &rest sexp)))
;; expand to function definitions and related forms on autoload gen
(autoload-macro expand))
(let* ((name (if (consp struct) (car struct) struct))
(warning nil)
(opts (cdr-safe struct))

View file

@ -222,10 +222,12 @@ INIT-VALUE LIGHTER KEYMAP.
(indent defun)
(debug (&define name string-or-null-p
[&optional [&not keywordp] sexp
&optional [&not keywordp] sexp
&optional [&not keywordp] sexp]
&optional [&not keywordp] sexp
&optional [&not keywordp] sexp]
[&rest [keywordp sexp]]
def-body)))
def-body))
;; expand to the command definition on autoload gen
(autoload-macro expand))
(let* ((last-message (make-symbol "last-message"))
(mode-name (symbol-name mode))
@ -488,7 +490,9 @@ after running the major mode's hook. However, MODE is not turned
on if the hook has explicitly disabled it.
\(fn GLOBAL-MODE MODE TURN-ON [KEY VALUE]... BODY...)"
(declare (doc-string 2) (indent defun))
(declare (doc-string 2) (indent defun)
;; expand to the minor-mode definition on autoload gen
(autoload-macro expand))
(let* ((global-mode-name (symbol-name global-mode))
(mode-name (symbol-name mode))
(pretty-name (easy-mmode-pretty-mode-name mode))

View file

@ -675,7 +675,8 @@ encapsulates the state of a computation that produces a sequence
of values. Callers can retrieve each value using `iter-next'."
(declare (indent defun)
(debug (&define name lambda-list lambda-doc &rest sexp))
(doc-string 3))
(doc-string 3)
(autoload-macro expand)) ; expand to the defun on autoload gen
(cl-assert lexical-binding)
(let* ((parsed-body (macroexp-parse-body body))
(declarations (car parsed-body))

View file

@ -135,7 +135,8 @@ After VARS is handled, BODY is evaluated in the new environment."
This is like `defmacro', but has several advantages.
See Info node `(elisp)Defining Functions' for more details."
;; FIXME: How can this work with CL arglists?
(declare (indent defun) (debug defun) (doc-string 3))
(declare (indent defun) (debug defun) (doc-string 3)
(autoload-macro expand)) ; expand to the defun on autoload gen
(let ((doc (if (stringp (car-safe body)) (list (pop body))))
(declares (if (eq (car-safe (car-safe body)) 'declare) (pop body)))
(cm-name (intern (format "%s--inliner" name)))

View file

@ -143,12 +143,37 @@ scanning for autoloads and will be in the `load-path'."
3)
form))
;; The following macros are known to define functions, and are treated
;; specially when encountered during autoload generation, translating
;; calls to them directly into appropriate (autoload function ...)
;; forms.
;;
;; An alternative to appearing on this list is for a macro to declare
;; (autoload-macro expand), so calls to it get expanded into more basic
;; forms during generation. Macros may be removed from this list once
;; they request such expansion and produce suitable output (e.g. by
;; employing :autoload-end to omit unneeded forms).
(defconst loaddefs--defining-macros
'( define-skeleton define-derived-mode define-compilation-mode
define-generic-mode define-globalized-minor-mode define-minor-mode
cl-defun defun* cl-defmacro defmacro* define-overloadable-function
transient-define-prefix transient-define-suffix transient-define-infix
transient-define-argument transient-define-group
;; Obsolete; keep until the alias is removed.
easy-mmode-define-global-mode
easy-mmode-define-minor-mode
define-global-minor-mode))
(defvar loaddefs--load-error-files nil)
(defun loaddefs-generate--make-autoload (form file &optional expansion)
"Turn FORM into an autoload or defvar for source file FILE.
Returns nil if FORM is not a special autoload form (i.e. a function definition
or macro definition or a defcustom).
If EXPANSION is non-nil, we're processing the macro expansion of an
expression, in which case we want to handle forms differently."
expression, in which case we want to handle forms differently.
Note that macros can request expansion by including `(autoload-macro
expand)' among their `declare' forms."
(let ((car (car-safe form)) expand)
(cond
((and expansion (eq car 'defalias))
@ -192,42 +217,40 @@ expression, in which case we want to handle forms differently."
(setq form (copy-sequence form))
(setcdr (memq :autoload-end form) nil))
(let ((exps (delq nil (mapcar (lambda (form)
(loaddefs-generate--make-autoload
form file expansion))
(unless (eq form :autoload-end)
(loaddefs-generate--make-autoload
form file expansion)))
(cdr form)))))
(when exps (cons 'progn exps)))))
;; For complex cases, try again on the macro-expansion.
((and (memq car '( define-globalized-minor-mode defun defmacro
define-minor-mode define-inline
cl-defun cl-defmacro cl-defgeneric
cl-defstruct pcase-defmacro iter-defun cl-iter-defun
;; Obsolete; keep until the alias is removed.
easy-mmode-define-global-mode
easy-mmode-define-minor-mode
define-global-minor-mode))
(macrop car)
(setq expand (let ((load-true-file-name file)
(load-file-name file))
(macroexpand form)))
(memq (car expand) '(progn prog1 defalias)))
;; For macros which request it, try again on their expansion.
((progn
;; If the car is an unknown symbol, we load the file first to
;; give packages a chance to define their macros.
(unless (or (not (symbolp car)) (fboundp car)
;; Special cases handled below
(memq car loaddefs--defining-macros)
(memq car '(defclass defcustom deftheme defgroup nil))
(assoc file load-history)
(member file loaddefs--load-error-files))
(let ((load-path (cons (file-name-directory file) load-path)))
(message "loaddefs-gen: loading file %s (for %s)" file car)
(condition-case e (load file)
(error
(push file loaddefs--load-error-files) ; do not attempt again
(warn "loaddefs-gen: load error\n\t%s" e)))))
(and (macrop car)
(eq 'expand (function-get car 'autoload-macro))
(setq expand (let ((load-true-file-name file)
(load-file-name file))
(macroexpand form)))
(not (eq car (car expand)))))
;; Recurse on the expansion.
(loaddefs-generate--make-autoload expand file 'expansion))
;; For special function-like operators, use the `autoload' function.
((memq car '( define-skeleton define-derived-mode
define-compilation-mode define-generic-mode
define-globalized-minor-mode
define-minor-mode
cl-defun defun* cl-defmacro defmacro*
define-overloadable-function
transient-define-prefix transient-define-suffix
transient-define-infix transient-define-argument
transient-define-group
;; Obsolete; keep until the alias is removed.
easy-mmode-define-global-mode
easy-mmode-define-minor-mode
define-global-minor-mode))
;; For known special macros which define functions, use `autoload'
;; directly.
((memq car loaddefs--defining-macros)
(let* ((macrop (memq car '(defmacro cl-defmacro defmacro*)))
(name (nth 1 form))
(args (pcase car

View file

@ -544,7 +544,9 @@ to this macro.
By convention, DOC should use \"EXPVAL\" to stand
for the result of evaluating EXP (first arg to `pcase').
\n(fn NAME ARGS [DOC] &rest BODY...)"
(declare (indent 2) (debug defun) (doc-string 3))
(declare (indent 2) (debug defun) (doc-string 3)
;; Expand to defun and related forms on autoload gen
(autoload-macro expand))
;; Add the function via `fsym', so that an autoload cookie placed
;; on a pcase-defmacro will cause the macro to be loaded on demand.
(let ((fsym (intern (format "%s--pcase-macroexpander" name)))