GNU bug report logs - #78995
[PATCH] ;;;autoload-expand for special macros

Previous Next

Package: emacs;

Reported by: JD Smith <jdtsmith <at> gmail.com>

Date: Fri, 11 Jul 2025 19:29:02 UTC

Severity: normal

Tags: patch

Fixed in version 31

Done: "J.D. Smith" <jdtsmith <at> gmail.com>

Full log


View this message in rfc822 format

From: "J.D. Smith" <jdtsmith <at> gmail.com>
To: Stefan Monnier <monnier <at> iro.umontreal.ca>
Cc: 78995 <at> debbugs.gnu.org
Subject: bug#78995: [PATCH] ;;;autoload-expand for special macros
Date: Sat, 12 Jul 2025 13:26:04 -0400
[Message part 1 (text/plain, inline)]
Stefan Monnier <monnier <at> iro.umontreal.ca> writes:

>> This makes it challenging to wrap things like `define-minor-mode' into a new
>> macro, and autoload the resulting forms.  This small patch adds a new
>> variant of the autoload cookie:
>>
>> ;;;autoload-expand
>> (my-autoloadable-macro-to-expand ...)
>
> I suspect this will often fail to work because autoloads are often
> generated by a fresh new session where `my-autoloadable-macro-to-expand`
> will not yet be defined.

Good point.  That chicken-and-egg problem was definitely in the back of
my mind, and is why I didn't attempt to create an expandable list to
replace the list of hard-coded special macros.

> I've been wanting to add the feature you propose, but this "macro is not
> defined" has gotten in the way until now.  I think we need to add
> something like a file-local `autoload-requires-for-macros`.

How about this solution?  If the car of the following form is not a
macro (and not a function), attempt to load the file itself (temporarily
expanding `load-path' to include the file's containing directory).  If
that fails to define the macro, warn the user, and simply proceed
without expansion.

[autoload-expand.patch (text/x-patch, inline)]
diff --git a/lisp/emacs-lisp/loaddefs-gen.el b/lisp/emacs-lisp/loaddefs-gen.el
index 0f136df1fe2..f7919a7a377 100644
--- a/lisp/emacs-lisp/loaddefs-gen.el
+++ b/lisp/emacs-lisp/loaddefs-gen.el
@@ -147,7 +147,10 @@ loaddefs-generate--make-autoload
 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.  If
+EXPANSION is the symbol `force' and the following form is a macro call,
+it will be expanded prior to generating autoload forms, similar to what
+is done automatically for special macros like `define-minor-mode'."
   (let ((car (car-safe form)) expand)
     (cond
      ((and expansion (eq car 'defalias))
@@ -196,12 +199,13 @@ loaddefs-generate--make-autoload
                                       (cdr form)))))
           (when exps (cons 'progn exps)))))
 
-     ;; For complex cases, try again on the macro-expansion.
-     ((and (memq car '(easy-mmode-define-global-mode define-global-minor-mode
-                       define-globalized-minor-mode defun defmacro
-		       easy-mmode-define-minor-mode define-minor-mode
-                       define-inline cl-defun cl-defmacro cl-defgeneric
-                       cl-defstruct pcase-defmacro iter-defun cl-iter-defun))
+     ;; For complex cases and forced expansions, try again on the macro-expansion.
+     ((and (or (eq expansion 'force)
+	       (memq car '(easy-mmode-define-global-mode define-global-minor-mode
+			    define-globalized-minor-mode defun defmacro
+			    easy-mmode-define-minor-mode define-minor-mode
+			    define-inline cl-defun cl-defmacro cl-defgeneric
+			    cl-defstruct pcase-defmacro iter-defun cl-iter-defun)))
            (macrop car)
 	   (setq expand (let ((load-true-file-name file)
                               (load-file-name file))
@@ -381,6 +385,10 @@ loaddefs-generate--parse-file
 setting of `generated-autoload-file' in FILE, and
 by ;;;###foo-autoload statements.
 
+The special statement ;;;###[foo-]autoload-expand causes the following
+form to be macro-expanded prior to generating autoloads; see
+`loaddefs-generate--make-autoload'.
+
 If PACKAGE-DATA is `only', return only the package data.  If t,
 include the package data with the rest of the data.  Otherwise,
 don't include."
@@ -465,16 +473,38 @@ loaddefs-generate--parse-file
                                 (expand-file-name
                                  (concat aname "-loaddefs.el")
                                  (file-name-directory file))
-                              (or local-outfile main-outfile))))
+                              (or local-outfile main-outfile)))
+		   (expansion (and (looking-at "-expand[ \t]*") 'force)))
+	      (when expansion
+		(goto-char (match-end 0)))
               (if (eolp)
                   ;; We have a form following.
                   (let* ((form (prog1
                                    (read (current-buffer))
                                  (unless (bolp)
                                    (forward-line 1))))
-                         (autoload (or (loaddefs-generate--make-autoload
-                                        form load-name)
-                                       form)))
+                         (car (car form))
+                         autoload)
+                    (when expansion
+                      ;; We encountered ;;;###autoload-expand, so the
+                      ;; following form should be a macro.  Check that
+                      ;; it is defined.  If it is undefined (and not a
+                      ;; function), load the file to attempt to define
+                      ;; it.  If it remains undefined, warn and simply
+                      ;; ignore the expand request.
+                      (unless (or (macrop car) (functionp car))
+                        (let ((load-path (cons (file-name-directory file) load-path)))
+                         (message "loaddefs-gen: loading file %s" file)
+                         (condition-case e (load file)
+                           (error
+                            (warn "loaddefs-gen: load error\n\t%s" e)))))
+                      (unless (macrop car)
+                        (warn "loaddefs-gen: macro undefined, skipping expansion\n\t%s"
+                              car)
+                        (setq expansion nil)))
+                    (setq autoload (or (loaddefs-generate--make-autoload
+                                        form load-name expansion)
+                                       form))
                     ;; We get back either an autoload form, or a tree
                     ;; structure of `(progn ...)' things, so unravel that.
                     (let ((forms (if (eq (car autoload) 'progn)

This bug report was last modified 27 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.