From 1d1b664fbb9232aa40d8daa54a689cfd63d38aa9 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier@iro.umontreal.ca>
Date: Mon, 31 Jan 2022 11:07:06 -0500
Subject: (function-history): New symbol property (bug#53632)

Rework the code we have in Fdefalias that tries to keep track
of definitions so as to be able to undo them later.

We used to store in `load-history` when an autoload is redefined as
a non-autoload and in the `autoload` symbol property we used to store
the autoload data that used to be used before it got overriden.

Instead, store the history of the function definition of
a symbol in its `function-history` symbol property.
To make this list cheap in the default case, the latest value is not stored
in the list (since it's in the `symbol-function`) and neither is the first
file.  So if there's only been a single definition (the most common case),
the list is empty and the property is just not present at all.

The patch also gets rid of the `autoload` vs `defun` distinction in
`load-history` which seems unnecessary (a significant part of the
motivation for this patch was to get rid of the special handling of
autoloads in this part of the code).

* src/data.c (add_to_function_history): New function.
(defalias): Use it.  Don't add the `t` entries for autoloads and always
use `defun` regardless of the kind of definition.
Change `Vautoload_queue` to only hold the function
symbols since the rest is now available from `function-history`.
* src/eval.c (un_autoload): Adjust accordingly.

* src/lread.c (load-history): Udate docstring.

* lisp/loadhist.el (loadhist-unload-filename): New var.
(unload-feature): Bind it.
(loadhist-unload-element): Document its availability.
(loadhist--restore-autoload): Delete var.
(loadhist--unload-function): Delete function.
(loadhist-unload-element): Delete the `t` and `autoload` methods.
Rewrite the `defun` method using `function-history`.

* lisp/help-fns.el: Require `seq`.
(help-fns--autoloaded-p): Rewrite.
(help-fns-function-description-header): Adjust call accordingly.

* doc/lispref/loading.texi (Where Defined): Remove `autoload` and `t`
entries from `load-history` since we don't generate them any more.
Document the `function-history` which replaces the `autoload` property.
(Unloading): Adjust symbol property name accordingly.

* test/lisp/loadhist-resources/loadhist--bar.el:
* test/lisp/loadhist-resources/loadhist--foo.el: New files.
* test/lisp/loadhist-tests.el (loadhist-tests-unload-feature-nested)
(loadhist-tests-unload-feature-notnested): New tests.
---
 src/data.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 50 insertions(+), 12 deletions(-)

(limited to 'src/data.c')

diff --git a/src/data.c b/src/data.c
index a5a76a27554..95d29ac9e98 100644
--- a/src/data.c
+++ b/src/data.c
@@ -859,6 +859,43 @@ DEFUN ("fset", Ffset, Sfset, 2, 2, 0,
   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)
 {
@@ -866,19 +903,19 @@ 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);
       }
   }
 
@@ -4171,6 +4208,7 @@ syms_of_data (void)
 
   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");
 
-- 
cgit v1.2.3