The symbol @var{var} was defined as a variable.
@item (defun . @var{fun})
The function @var{fun} was defined.
-@item (t . @var{fun})
-The function @var{fun} was previously an autoload before this library
-redefined it as a function. The following element is always
@code{(defun . @var{fun})}, which represents defining @var{fun} as a
function.
-@item (autoload . @var{fun})
-The function @var{fun} was defined as an autoload.
@item (defface . @var{face})
The face @var{face} was defined.
@item (require . @var{feature})
by adding the symbols defined to the element for the file being visited,
rather than replacing that element. @xref{Eval}.
+In addition to @code{load-history}, every function keeps track of its
+own history in the symbol property @code{function-history}.
+The reason why functions are treated specially in this respect is that
+it is common for functions to be defined in two steps in two different
+files (typically, one of them is an autoload), so in order to be
+able to properly @emph{unload} a file, we need to know more precisely
+what that file did to the function definition.
+
+@kindex{function-history}
+The symbol property @code{function-history} holds a list of the form
+@code{(@var{file1} @var{def2} @var{file2} @var{def3} ...)} where
+@var{file1} is the last file that changed the definition and
+@var{def2} was the definition before @var{file1}, set by @var{file2},
+etc... Logically this list should end with the name of the first file
+that defined this function, but to save space this last element
+is usually omitted.
+
@node Unloading
@section Unloading
@cindex unloading packages
library with @code{defun}, @code{defalias}, @code{defsubst},
@code{defmacro}, @code{defconst}, @code{defvar}, and @code{defcustom}.
It then restores any autoloads formerly associated with those symbols.
-(Loading saves these in the @code{autoload} property of the symbol.)
+(Loading saves these in the @code{function-history} property of the symbol.)
Before restoring the previous definitions, @code{unload-feature} runs
@code{remove-hook} to remove functions defined by the library from certain
delete the entire sequence, not just a single character at its
beginning.
+** 'load-history' does not treat autoloads specially any more.
+An autoload definition appears just as a (defun . NAME) and the
+(t . NAME) entries are not generated any more.
\f
* Changes in Emacs 29.1
when used as part of a property list specification for the
':underline' attribute.
+** 'defalias' records a more precise history of definitions.
+This is recorded in the `function-history` symbol property.
+
\f
* Changes in Emacs 29.1 on Non-Free Operating Systems
;;; Code:
(require 'cl-lib)
+(require 'seq)
(require 'help-mode)
(require 'radix-tree)
(eval-when-compile (require 'subr-x)) ;For when-let.
(terpri)))
;; We could use `symbol-file' but this is a wee bit more efficient.
-(defun help-fns--autoloaded-p (function file)
- "Return non-nil if FUNCTION has previously been autoloaded.
-FILE is the file where FUNCTION was probably defined."
- (let* ((file (file-name-sans-extension (file-truename file)))
- (load-hist load-history)
- (target (cons t function))
- found)
- (while (and load-hist (not found))
- (and (stringp (caar load-hist))
- (equal (file-name-sans-extension (caar load-hist)) file)
- (setq found (member target (cdar load-hist))))
- (setq load-hist (cdr load-hist)))
- found))
+(defun help-fns--autoloaded-p (function)
+ "Return non-nil if FUNCTION has previously been autoloaded."
+ (seq-some #'autoloadp (get function 'function-history)))
(defun help-fns--interactive-only (function)
"Insert some help blurb if FUNCTION should only be used interactively."
"Print a line describing FUNCTION to `standard-output'."
(pcase-let* ((`(,_real-function ,def ,aliased ,real-def)
(help-fns--analyze-function function))
- (file-name (find-lisp-object-file-name function (if aliased 'defun
- def)))
+ (file-name (find-lisp-object-file-name
+ function (if aliased 'defun def)))
(beg (if (and (or (byte-code-function-p def)
(keymapp def)
(memq (car-safe def) '(macro lambda closure)))
(stringp file-name)
- (help-fns--autoloaded-p function file-name))
+ (help-fns--autoloaded-p function))
(concat
"an autoloaded " (if (commandp def)
"interactive "))
;; mode, or proposed is not nil and not major-mode, and so we use it.
(funcall (or proposed 'fundamental-mode)))))))
+(defvar loadhist-unload-filename nil)
+
(cl-defgeneric loadhist-unload-element (x)
- "Unload an element from the `load-history'."
+ "Unload an element from the `load-history'.
+The variable `loadhist-unload-filename' holds the name of the file we're
+unloading."
(message "Unexpected element %S in load-history" x))
-;; In `load-history', the definition of a previously autoloaded
-;; function is represented by 2 entries: (t . SYMBOL) comes before
-;; (defun . SYMBOL) and says we should restore SYMBOL's autoload when
-;; we undefine it.
-;; So we use this auxiliary variable to keep track of the last (t . SYMBOL)
-;; that occurred.
-(defvar loadhist--restore-autoload nil
- "If non-nil, is a symbol for which to try to restore a previous autoload.")
-
-(cl-defmethod loadhist-unload-element ((x (head t)))
- (setq loadhist--restore-autoload (cdr x)))
-
-(defun loadhist--unload-function (x)
- (let ((fun (cdr x)))
- (when (fboundp fun)
- (when (fboundp 'ad-unadvise)
- (ad-unadvise fun))
- (let ((aload (get fun 'autoload)))
- (defalias fun
- (if (and aload (eq fun loadhist--restore-autoload))
- (cons 'autoload aload)
- nil)))))
- (setq loadhist--restore-autoload nil))
-
(cl-defmethod loadhist-unload-element ((x (head defun)))
- (loadhist--unload-function x))
-(cl-defmethod loadhist-unload-element ((x (head autoload)))
- (loadhist--unload-function x))
+ (let* ((fun (cdr x))
+ (hist (get fun 'function-history)))
+ (cond
+ ((null hist)
+ (defalias fun nil)
+ ;; Override the change that `defalias' just recorded.
+ (put fun 'function-history nil))
+ ((equal (car hist) loadhist-unload-filename)
+ (defalias fun (cadr hist))
+ ;; Set the history afterwards, to override the change that
+ ;; `defalias' records otherwise.
+ (put fun 'function-history (cddr hist)))
+ (t
+ ;; Unloading a file whose definition is "inactive" (i.e. has been
+ ;; overridden by another file): just remove it from the history,
+ ;; so future unloading of that other file has a chance to DTRT.
+ (let* ((tmp (plist-member hist loadhist-unload-filename))
+ (pos (- (length hist) (length tmp))))
+ (cl-assert (> pos 1))
+ (setcdr (nthcdr (- pos 2) hist) (cdr tmp)))))))
(cl-defmethod loadhist-unload-element ((_ (head require))) nil)
(cl-defmethod loadhist-unload-element ((_ (head defface))) nil)
(prin1-to-string dependents) file))))
(let* ((unload-function-defs-list (feature-symbols feature))
(file (pop unload-function-defs-list))
+ (loadhist-unload-filename file)
(name (symbol-name feature))
(unload-hook (intern-soft (concat name "-unload-hook")))
(unload-func (intern-soft (concat name "-unload-function"))))
return definition;
}
+static void
+add_to_function_history (Lisp_Object symbol, Lisp_Object olddef)
+{
+ eassert (!NILP (olddef));
+
+ Lisp_Object past = Fget (symbol, Qfunction_history);
+ Lisp_Object file = Qnil;
+ /* FIXME: Sadly, `Vload_file_name` gives less precise information
+ (it's sometimes non-nil when it shoujld be nil). */
+ Lisp_Object tail = Vcurrent_load_list;
+ FOR_EACH_TAIL_SAFE (tail)
+ if (NILP (XCDR (tail)) && STRINGP (XCAR (tail)))
+ file = XCAR (tail);
+
+ Lisp_Object tem = Fplist_member (past, file);
+ if (!NILP (tem))
+ { /* New def from a file used before.
+ Overwrite the previous record associated with this file. */
+ if (EQ (tem, past))
+ /* The new def is from the same file as the last change, so
+ there's nothing to do: unloading the file should revert to
+ the status before the last change rather than before this load. */
+ return;
+ Lisp_Object pastlen = Flength (past);
+ Lisp_Object temlen = Flength (tem);
+ EMACS_INT tempos = XFIXNUM (pastlen) - XFIXNUM (temlen);
+ eassert (tempos > 1);
+ Lisp_Object prev = Fnthcdr (make_fixnum (tempos - 2), past);
+ /* Remove the previous info for this file.
+ E.g. change `hist` from (... OTHERFILE DEF3 THISFILE DEF2 ...)
+ to (... OTHERFILE DEF2). */
+ XSETCDR (prev, XCDR (tem));
+ }
+ /* Push new def from new file. */
+ Fput (symbol, Qfunction_history, Fcons (file, Fcons (olddef, past)));
+}
+
void
defalias (Lisp_Object symbol, Lisp_Object definition)
{
bool autoload = AUTOLOADP (definition);
if (!will_dump_p () || !autoload)
{ /* Only add autoload entries after dumping, because the ones before are
- not useful and else we get loads of them from the loaddefs.el. */
- Lisp_Object function = XSYMBOL (symbol)->u.s.function;
-
- if (AUTOLOADP (function))
- /* Remember that the function was already an autoload. */
- LOADHIST_ATTACH (Fcons (Qt, symbol));
- LOADHIST_ATTACH (Fcons (autoload ? Qautoload : Qdefun, symbol));
-
- if (!NILP (Vautoload_queue) && !NILP (function))
- Vautoload_queue = Fcons (Fcons (symbol, function), Vautoload_queue);
+ not useful and else we get loads of them from the loaddefs.el.
+ That saves us about 110KB in the pdmp file (Jan 2022). */
+ LOADHIST_ATTACH (Fcons (Qdefun, symbol));
+ }
+ }
- if (AUTOLOADP (function))
- Fput (symbol, Qautoload, XCDR (function));
+ {
+ Lisp_Object olddef = XSYMBOL (symbol)->u.s.function;
+ if (!NILP (olddef))
+ {
+ if (!NILP (Vautoload_queue))
+ Vautoload_queue = Fcons (symbol, Vautoload_queue);
+ add_to_function_history (symbol, olddef);
}
}
DEFSYM (Qinteractive_form, "interactive-form");
DEFSYM (Qdefalias_fset_function, "defalias-fset-function");
+ DEFSYM (Qfunction_history, "function-history");
DEFSYM (Qbyte_code_function_p, "byte-code-function-p");
static void
un_autoload (Lisp_Object oldqueue)
{
- Lisp_Object queue, first, second;
-
/* Queue to unwind is current value of Vautoload_queue.
oldqueue is the shadowed value to leave in Vautoload_queue. */
- queue = Vautoload_queue;
+ Lisp_Object queue = Vautoload_queue;
Vautoload_queue = oldqueue;
while (CONSP (queue))
{
- first = XCAR (queue);
- second = Fcdr (first);
- first = Fcar (first);
- if (EQ (first, make_fixnum (0)))
- Vfeatures = second;
+ Lisp_Object first = XCAR (queue);
+ if (CONSP (first) && EQ (XCAR (first), make_fixnum (0)))
+ Vfeatures = XCDR (first);
else
- Ffset (first, second);
+ Ffset (first, Fcar (Fcdr (Fget (first, Qfunction_history))));
queue = XCDR (queue);
}
}
The remaining ENTRIES in the alist element describe the functions and
variables defined in that file, the features provided, and the
features required. Each entry has the form `(provide . FEATURE)',
-`(require . FEATURE)', `(defun . FUNCTION)', `(autoload . SYMBOL)',
-`(defface . SYMBOL)', `(define-type . SYMBOL)',
-`(cl-defmethod METHOD SPECIALIZERS)', or `(t . SYMBOL)'.
-Entries like `(t . SYMBOL)' may precede a `(defun . FUNCTION)' entry,
-and mean that SYMBOL was an autoload before this file redefined it
-as a function. In addition, entries may also be single symbols,
+`(require . FEATURE)', `(defun . FUNCTION)', `(defface . SYMBOL)',
+ `(define-type . SYMBOL)', or `(cl-defmethod METHOD SPECIALIZERS)'.
+In addition, entries may also be single symbols,
which means that symbol was defined by `defvar' or `defconst'.
During preloading, the file name recorded is relative to the main Lisp
--- /dev/null
+;;; loadhist--bar.el --- Dummy package for loadhist-tests -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(autoload 'loadhist--foo-inc "loadhist--foo")
+
+(defun loadhist--bar-dec (x) (1- x))
+
+(provide 'loadhist--bar)
+;;; loadhist--bar.el ends here
--- /dev/null
+;;; loadhist--foo.el --- Dummy package for loadhist-tests -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(autoload 'loadhist--bar-dec "loadhist--bar")
+
+(defun loadhist--foo-inc (x) (1+ x))
+
+(provide 'loadhist--foo)
+;;; loadhist--foo.el ends here
(should-error (unload-feature 'dired))
(unload-feature 'dired-x))
+(defvar loadhist--tests-dir (file-name-directory (macroexp-file-name)))
+
+(ert-deftest loadhist-tests-unload-feature-nested ()
+ (add-to-list 'load-path (expand-file-name
+ "loadhist-resources/"
+ loadhist--tests-dir))
+ (declare-function loadhist--foo-inc "loadhist--foo")
+ (declare-function loadhist--bar-dec "loadhist--dec")
+ (load "loadhist--foo" nil t)
+ (should (and (functionp 'loadhist--bar-dec) (functionp 'loadhist--foo-inc)))
+ (should (autoloadp (symbol-function 'loadhist--bar-dec)))
+ (load "loadhist--bar" nil t)
+ (should (and (functionp 'loadhist--bar-dec) (functionp 'loadhist--foo-inc)))
+ (should (not (autoloadp (symbol-function 'loadhist--bar-dec))))
+ (should (not (autoloadp (symbol-function 'loadhist--foo-inc))))
+ (should (equal (list 40 42)
+ (list (loadhist--bar-dec 41) (loadhist--foo-inc 41))))
+ (unload-feature 'loadhist--bar)
+ (should (and (functionp 'loadhist--bar-dec) (functionp 'loadhist--foo-inc)))
+ (should (autoloadp (symbol-function 'loadhist--bar-dec)))
+ (should (not (autoloadp (symbol-function 'loadhist--foo-inc))))
+ (unload-feature 'loadhist--foo)
+ (should (null (symbol-function 'loadhist--bar-dec)))
+ (should (null (symbol-function 'loadhist--foo-inc)))
+ (should (null (get 'loadhist--bar-dec 'function-history)))
+ (should (null (get 'loadhist--foo-inc 'function-history))))
+
+(ert-deftest loadhist-tests-unload-feature-notnested ()
+ (add-to-list 'load-path (expand-file-name
+ "loadhist-resources/"
+ loadhist--tests-dir))
+ (load "loadhist--foo" nil t)
+ (load "loadhist--bar" nil t)
+ (should (equal (list 40 42)
+ (list (loadhist--bar-dec 41) (loadhist--foo-inc 41))))
+ (unload-feature 'loadhist--foo)
+ (should (functionp 'loadhist--bar-dec))
+ (should (not (autoloadp (symbol-function 'loadhist--bar-dec))))
+ (should (let ((f (symbol-function 'loadhist--foo-inc)))
+ ;; Both choices seem acceptable.
+ (or (null f) (autoloadp f))))
+ (unload-feature 'loadhist--bar)
+ (should (null (symbol-function 'loadhist--bar-dec)))
+ (should (null (symbol-function 'loadhist--foo-inc)))
+ (should (null (get 'loadhist--bar-dec 'function-history)))
+ (should (null (get 'loadhist--foo-inc 'function-history))))
+
;;; loadhist-tests.el ends here