From ec22e923c0dab286cdcf18595e836f1013e9a9f1 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Wed, 3 Aug 2022 17:16:09 +0300 Subject: [PATCH] Teach 'symbol-file' about .eln natively-compiled files * lisp/subr.el (locate-eln-file): New function. (symbol-file): Accept an optional 3rd argument NATIVE-P, and, if non-nil, try to locate and report the .eln file where SYMBOL was defined. * etc/NEWS: * doc/lispref/loading.texi (Where Defined): Document the new optional argument of 'symbol-file'. --- doc/lispref/loading.texi | 10 +++- etc/NEWS | 5 ++ lisp/subr.el | 103 +++++++++++++++++++++++++++++++-------- 3 files changed, 96 insertions(+), 22 deletions(-) diff --git a/doc/lispref/loading.texi b/doc/lispref/loading.texi index 54fc16ec9f0..e8dce433a5b 100644 --- a/doc/lispref/loading.texi +++ b/doc/lispref/loading.texi @@ -1032,7 +1032,7 @@ with a call to @code{provide}. The order of the elements in the @cindex symbol, where defined @cindex where was a symbol defined -@defun symbol-file symbol &optional type +@defun symbol-file symbol &optional type native-p This function returns the name of the file that defined @var{symbol}. If @var{type} is @code{nil}, then any kind of definition is acceptable. If @var{type} is @code{defun}, @code{defvar}, or @code{defface}, that @@ -1043,6 +1043,14 @@ The value is normally an absolute file name. It can also be @code{nil}, if the definition is not associated with any file. If @var{symbol} specifies an autoloaded function, the value can be a relative file name without extension. + +If the optional third argument @var{native-p} is non-@code{nil}, and +Emacs was built with native compilation support (@pxref{Native +Compilation}), this function will try to find the @file{.eln} file +that defined @var{symbol}, instead of the @file{.elc} or @file{.el} +file. If such a @file{.eln} file is found and is not outdated, the +function will return its absolute file name; otherwise it will report +the name of either the source or the byte-compiled file. @end defun The basis for @code{symbol-file} is the data in the variable diff --git a/etc/NEWS b/etc/NEWS index b88fb636623..7e8ed465eb6 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2575,6 +2575,11 @@ things to be saved. ** New function 'string-equal-ignore-case'. This compares strings ignoring case differences. +** 'symbol-file' can now report natively-compiled .eln files. +If Emacs was built with native-compilation enabled, Lisp programs can +now call 'symbol-file' with the new optional 3rd argument non-nil to +request the name of the .eln file which defined a given symbol. + ** Themes --- diff --git a/lisp/subr.el b/lisp/subr.el index ff82d0d1d8d..1b59db0604c 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -2700,18 +2700,44 @@ This is to `put' what `defalias' is to `fset'." (setcdr ps (cons symbol (cdr ps)))))) (put symbol prop val)) -(defun symbol-file (symbol &optional type) +(defvar comp-native-version-dir) +(defvar native-comp-eln-load-path) +(declare-function subr-native-elisp-p "data.c") +(declare-function native-comp-unit-file "data.c") +(declare-function subr-native-comp-unit "data.c") +(declare-function comp-el-to-eln-rel-filename "comp.c") + +(defun locate-eln-file (eln-file) + "Locate a natively-compiled ELN-FILE by searching its load path. +This function looks in directories named by `native-comp-eln-load-path'." + (or (locate-file-internal (concat comp-native-version-dir "/" eln-file) + native-comp-eln-load-path) + (locate-file-internal + ;; Preloaded *.eln files live in the preloaded/ subdirectory of + ;; the last entry in `native-comp-eln-load-path'. + (concat comp-native-version-dir "/preloaded/" eln-file) + (last native-comp-eln-load-path)))) + +(defun symbol-file (symbol &optional type native-p) "Return the name of the file that defined SYMBOL. The value is normally an absolute file name. It can also be nil, if the definition is not associated with any file. If SYMBOL specifies an autoloaded function, the value can be a relative file name without extension. -If TYPE is nil, then any kind of definition is acceptable. If -TYPE is `defun', `defvar', or `defface', that specifies function +If TYPE is nil, then any kind of SYMBOL's definition is acceptable. +If TYPE is `defun', `defvar', or `defface', that specifies function definition, variable definition, or face definition only. Otherwise TYPE is assumed to be a symbol property. +If NATIVE-P is non-nil, and SYMBOL was loaded from a .eln file, +this function will return the absolute file name of that .eln file, +if found. Note that if the .eln file is older than its source .el +file, Emacs won't load such an outdated .eln file, and this function +will not return it. If the .eln file couldn't be found, or is +outdated, the function returns the corresponding .elc or .el file +instead. + This function only works for symbols defined in Lisp files. For symbols that are defined in C files, use `help-C-file-name' instead." @@ -2719,24 +2745,59 @@ instead." (symbolp symbol) (autoloadp (symbol-function symbol))) (nth 1 (symbol-function symbol)) - (catch 'found - (pcase-dolist (`(,file . ,elems) load-history) - (when (if type - (if (eq type 'defvar) - ;; Variables are present just as their names. - (member symbol elems) - ;; Many other types are represented as (TYPE . NAME). - (or (member (cons type symbol) elems) - (memq symbol (alist-get type - (alist-get 'define-symbol-props - elems))))) - ;; We accept all types, so look for variable def - ;; and then for any other kind. - (or (member symbol elems) - (let ((match (rassq symbol elems))) - (and match - (not (eq 'require (car match))))))) - (throw 'found file)))))) + (if (and native-p (or (null type) (eq type 'defun)) + (symbolp symbol) + (native-comp-available-p) + ;; If it's a defun, we have a shortcut. + (subr-native-elisp-p (symbol-function symbol))) + ;; native-comp-unit-file returns unnormalized file names. + (expand-file-name (native-comp-unit-file (subr-native-comp-unit + (symbol-function symbol)))) + (let ((elc-file + (catch 'found + (pcase-dolist (`(,file . ,elems) load-history) + (when (if type + (if (eq type 'defvar) + ;; Variables are present just as their + ;; names. + (member symbol elems) + ;; Many other types are represented as + ;; (TYPE . NAME). + (or (member (cons type symbol) elems) + (memq + symbol + (alist-get type + (alist-get 'define-symbol-props + elems))))) + ;; We accept all types, so look for variable def + ;; and then for any other kind. + (or (member symbol elems) + (let ((match (rassq symbol elems))) + (and match + (not (eq 'require (car match))))))) + (throw 'found file)))))) + ;; If they asked for the .eln file, try to find it. + (or (and elc-file + native-p + (native-comp-available-p) + (let* ((sans-ext (file-name-sans-extension elc-file)) + (el-file + (and (fboundp 'zlib-available-p) + (zlib-available-p) + (concat sans-ext ".el.gz"))) + (el-file-backup (concat sans-ext ".el"))) + (or (and el-file (file-exists-p el-file)) + (and (file-exists-p el-file-backup) + (setq el-file el-file-backup)) + (setq el-file nil)) + (when (stringp el-file) + (let ((eln-file (locate-eln-file + (comp-el-to-eln-rel-filename el-file)))) + ;; Emacs will not load an outdated .eln file, + ;; so we mimic this behavior here. + (if (file-newer-than-file-p eln-file el-file) + eln-file))))) + elc-file))))) (declare-function read-library-name "find-func" nil) -- 2.39.5