From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of text editors" Newsgroups: gmane.emacs.bugs Subject: bug#53632: Function definition history Date: Sun, 30 Jan 2022 00:07:57 -0500 Message-ID: Reply-To: Stefan Monnier Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="9460"; mail-complaints-to="usenet@ciao.gmane.io" To: 53632@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Sun Jan 30 06:09:23 2022 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nE2So-0002Hg-E1 for geb-bug-gnu-emacs@m.gmane-mx.org; Sun, 30 Jan 2022 06:09:22 +0100 Original-Received: from localhost ([::1]:33992 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1nE2Sn-0002o9-0c for geb-bug-gnu-emacs@m.gmane-mx.org; Sun, 30 Jan 2022 00:09:21 -0500 Original-Received: from eggs.gnu.org ([209.51.188.92]:54236) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1nE2SV-0002n0-3X for bug-gnu-emacs@gnu.org; Sun, 30 Jan 2022 00:09:03 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]:42478) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1nE2SU-0006Kn-1L for bug-gnu-emacs@gnu.org; Sun, 30 Jan 2022 00:09:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1nE2ST-0004nV-Ti for bug-gnu-emacs@gnu.org; Sun, 30 Jan 2022 00:09:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Stefan Monnier Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sun, 30 Jan 2022 05:09:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 53632 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.164351930718396 (code B ref -1); Sun, 30 Jan 2022 05:09:01 +0000 Original-Received: (at submit) by debbugs.gnu.org; 30 Jan 2022 05:08:27 +0000 Original-Received: from localhost ([127.0.0.1]:35381 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1nE2Ru-0004me-CS for submit@debbugs.gnu.org; Sun, 30 Jan 2022 00:08:27 -0500 Original-Received: from lists.gnu.org ([209.51.188.17]:35926) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1nE2Rr-0004mV-B0 for submit@debbugs.gnu.org; Sun, 30 Jan 2022 00:08:24 -0500 Original-Received: from eggs.gnu.org ([209.51.188.92]:54230) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1nE2Rq-0002fD-9V for bug-gnu-emacs@gnu.org; Sun, 30 Jan 2022 00:08:22 -0500 Original-Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:60220) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1nE2Rm-0006In-2g for bug-gnu-emacs@gnu.org; Sun, 30 Jan 2022 00:08:20 -0500 Original-Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 8F123100287 for ; Sun, 30 Jan 2022 00:08:11 -0500 (EST) Original-Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id EC15B1000F8 for ; Sun, 30 Jan 2022 00:08:07 -0500 (EST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1643519287; bh=7NGuzLgxLngcYBmar/TX10iBKie03HEwy2iAB1zi2rc=; h=From:To:Subject:Date:From; b=k10wAEAHN8imbKbcmD7wEaIMjnsAurgCXPBRKGi70R1ShXxvRT9SGCym4fs4XkYCI wj1y8eGZ2Lwo8WvHLDWHX4H60rbYRE+kND3PmxokLrfEoJ8sUURTG4r9gWaFW8AFET 4BeNSj83HmGGzWGoX5rJQMs1UemfxDAZf+WLqiyraMpEK+UK/KqOKWKmJEGWaRQaL9 pdu4EOgVrUSa+xuy5wDkolkhW79Egd+w7qmJqwOqGAyyKCrOj2rPBlwSxmVATwQp8C gCjxPFFxUpIggOTIKsubQgKN6bn/I79FqVVQaLf0Ijj3bs5yAqJJfyybt1m6noXPxf kz789Gpj/A6wQ== Original-Received: from ceviche (76-10-138-212.dsl.teksavvy.com [76.10.138.212]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 9A54C12077C for ; Sun, 30 Jan 2022 00:08:07 -0500 (EST) Received-SPF: pass client-ip=132.204.25.50; envelope-from=monnier@iro.umontreal.ca; helo=mailscanner.iro.umontreal.ca X-Spam_score_int: -42 X-Spam_score: -4.3 X-Spam_bar: ---- X-Spam_report: (-4.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:225581 Archived-At: --=-=-= Content-Type: text/plain Tags: patch I don't much like the code we have in Fdefalias that tries to keep track of definitions so as to be able to undo them later. It's too ad-hoc for its own good. The patch below tries to make it a bit better defined. 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. I suggest to replace that info with something slightly more complete. In the patch below I store the history of the function definition of a symbol in its `function-history` symbol property. This history is stored as a list of the form (... VAL(n+1) FILE(n+1) VALn FILEn ...) where VALn is the value set by FILEn. 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. If a function was first defined as an autoload and then overriden by the actual function definition, then the list will hold (FILE2 AUTOLOAD), i.e. the name of the file that provided the actual function definition and the autoload that was used before that. [ Note: the name of the file that provided the first definition can be recovered if really needed by checking all entries in `load-history`. In the patch below I have not needed it. ] This makes it possible to handle correctly things like unloading `cl-loaddefs.el` which should remove the autoloads that are still autoloads and leave untouched the functions whose autoload have been replaced by the actual function definition. In my tests it increased the size of the .pdmp by about 2KB (on a 32bit build). 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). At least I couldn't find any place in the code which took advantage of that distinction. Comments? Objections? Stefan In GNU Emacs 29.0.50 (build 1, i686-pc-linux-gnu, GTK+ Version 2.24.33, cairo version 1.16.0) of 2022-01-14 built on ceviche Repository revision: 161657c1e1598b41c82fcc740ec13b539b013191 Repository branch: work Windowing system distributor 'The X.Org Foundation', version 11.0.12013000 System Description: Debian GNU/Linux bookworm/sid Configured using: 'configure -C --enable-checking --with-modules --enable-check-lisp-object-type 'CFLAGS=-Wall -g3 -Og -Wno-pointer-sign' PKG_CONFIG_PATH=/home/monnier/lib/pkgconfig' --=-=-= Content-Type: text/patch Content-Disposition: attachment; filename=function-history.patch diff --git a/lisp/help-fns.el b/lisp/help-fns.el index 98a1b11e08..36c7966919 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -33,6 +33,7 @@ ;;; Code: (require 'cl-lib) +(require 'seq) (require 'help-mode) (require 'radix-tree) (eval-when-compile (require 'subr-x)) ;For when-let. @@ -678,19 +679,9 @@ help-fns--globalized-minor-mode (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." @@ -873,13 +864,13 @@ help-fns-function-description-header "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 ")) diff --git a/lisp/loadhist.el b/lisp/loadhist.el index 48058f4053..8f634afaca 100644 --- a/lisp/loadhist.el +++ b/lisp/loadhist.el @@ -157,38 +157,30 @@ unload--set-major-mode ;; 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)) + ((equal (car hist) loadhist-unload-filename) + (put fun 'function-history (cddr hist)) + (defalias fun (cadr 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 (1- pos) hist) (cdr tmp))))))) (cl-defmethod loadhist-unload-element ((_ (head require))) nil) (cl-defmethod loadhist-unload-element ((_ (head defface))) nil) @@ -257,6 +249,7 @@ unload-feature (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")))) diff --git a/src/data.c b/src/data.c index a5a76a2755..8cd13f3a77 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) { @@ -867,28 +904,24 @@ defalias (Lisp_Object symbol, Lisp_Object 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); - - if (AUTOLOADP (function)) - Fput (symbol, Qautoload, XCDR (function)); + LOADHIST_ATTACH (Fcons (Qdefun, symbol)); } } - { /* Handle automatic advice activation. */ - Lisp_Object hook = Fget (symbol, Qdefalias_fset_function); - if (!NILP (hook)) - call2 (hook, symbol, definition); - else - Ffset (symbol, definition); - } + 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); + } + + /* Handle automatic advice activation. */ + Lisp_Object hook = Fget (symbol, Qdefalias_fset_function); + if (!NILP (hook)) + call2 (hook, symbol, definition); + else + Ffset (symbol, definition); } DEFUN ("defalias", Fdefalias, Sdefalias, 2, 3, 0, @@ -4171,6 +4204,7 @@ #define PUT_ERROR(sym, tail, msg) \ 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"); diff --git a/src/eval.c b/src/eval.c index b083a00a79..cf38e76718 100644 --- a/src/eval.c +++ b/src/eval.c @@ -2236,35 +2236,40 @@ DEFUN ("autoload", Fautoload, Sautoload, 2, 5, 0, && !AUTOLOADP (XSYMBOL (function)->u.s.function)) return Qnil; + ptrdiff_t count = SPECPDL_INDEX (); + if (will_dump_p) + /* Only add autoload entries after dumping, because the ones before are + not useful and else we get loads of them from the loaddefs.el. */ + specbind (Qcurrent_load_list, Qnil); + if (!NILP (Vpurify_flag) && EQ (docstring, make_fixnum (0))) /* `read1' in lread.c has found the docstring starting with "\ and assumed the docstring will be provided by Snarf-documentation, so it passed us 0 instead. But that leads to accidental sharing in purecopy's hash-consing, so we use a (hopefully) unique integer instead. */ docstring = make_ufixnum (XHASH (function)); - return Fdefalias (function, - list5 (Qautoload, file, docstring, interactive, type), - Qnil); + Lisp_Object tem + = Fdefalias (function, + list5 (Qautoload, file, docstring, interactive, type), + Qnil); + unbind_to (count, Qnil); + return tem; } 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); } } diff --git a/src/lread.c b/src/lread.c index 9910db27de..ada137ff19 100644 --- a/src/lread.c +++ b/src/lread.c @@ -5240,12 +5248,9 @@ syms_of_lread (void) 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 --=-=-=--