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>
Message #20 received at 78995 <at> debbugs.gnu.org (full text, mbox):
From: "J.D. Smith" <jdtsmith <at> gmail.com> To: Stefan Monnier <monnier <at> iro.umontreal.ca> Cc: 78995 <at> debbugs.gnu.org Subject: Re: 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)
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.