函数帮助里面的链接为何会指向配置文件?

原本函数的帮助页面会指向函数定义所在的 el 文件(比如图中的 yas-text 给出了 yasnippet.el 的链接)但是我发现有些函数给出的地址是我的配置文件(比如图中的的 yas-global-mode 给出的链接是 ../emacs-config.el),导致无法直接跳转到函数定义。请问大家有遇到这种情况没?有没有什么解决方法?

我的配置文件是 org 生成的 el,并通过 init.el 里面的 load 函数进行加载。Emacs 版本是 master 版本前几天编译的,但是这个问题很久之前就出现过。猜测可能和 use-package 有关?

补充一个配置:

;; init.el 文件内容
(setq gc-cons-threshold (* 400 (expt 2 20))
      gc-cons-percentage 0.6)

 (let* ((file-name-handler-alist nil)
        (read-process-output-max (expt 2 22)))

    (load "~/.emacs.d/emacs-config.el")

    ;; load-history 是在这里变化的
    )

(setq gc-cons-threshold (expt 2 23)
      gc-cons-percentage 0.1)
;; emacs-config.el 文件内容
(setq custom-file "~/.emacs.d/customs.el")
(load custom-file t)

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(setq use-package-compute-statistics t)
(straight-use-package 'use-package)
(setq straight-use-package-by-default t) ; Ensure :straight t

(use-package yasnippet
  :init (yas-global-mode 1))
(use-package yasnippet-snippets
  :after yasnippet)

你的配置文件编译过吗?

没有。只有 emacs-config.el ,没有 elc 和 eln。

檢查下 load-history 变量的內容,用 add-variable-watcher 定位是什么地方改了这个变量

use-package 不太可能有关系,更可能是 org tangle 的问题。

本身 “autoloaded” 和 “byte-compiled” 不应该同时出现。

1 个赞

load-history 里面 yas-global-mode 出现了两次,位置靠前的一次是在 emacs-config.el 文件下面,另外一次是在 yasnippet.elc 文件下面。 猜测帮助界面里找到了第一个匹配的位置就直接返回了,所以返回的文件是 emacs-config.el

我这边 org=>el 是手动进行的,所以在 Emacs 启动的时候并不会进行 org tangle。

我在加载 emacs-config.el 之前,执行了下面这句,但启动的时候并没有打印出东西来 :rofl:

(add-variable-watcher 'load-history #'(lambda (sym new op where) (print "HIHI")))

那就单纯打印变量先定位到什么时候变了

load 不会改变定义的位置,可能是你在 emacs-config.el 执行了 eval 之类的操作。

最好把配置精简一下,验证是否仍然存在问题,把有问题的 emacs-config.el 贴出来。

补充了一下配置,没有明显的 eval ,依然是显示

yas-global-mode is an autoloaded interactive byte-compiled Lisp function in ‘…/…/…/emacs-config.el’.

就在那句 load 之后才变的,在 emacs-config.el 的最后一行都还没有。 这个也符合预期,load 之后才会加载 load-history 里。

我也遇到了类似的情况,感觉可能和 master 版本相关

同样的配置,上面的是 28.1.1,下面的是最近几天装的 master 版本

1 个赞

那就是最新版本的bug了

1 个赞

我刚编译的29 on macOS,好像没这个问题

应该不是 Emacs 版本的问题,至少我没有观察到 29.0 有什么不同。

加载时机/顺序的可能性更大。可以用 -Q 做个测试,常规加载方式可能有些隐含的步骤你没有发现:

$ tree /path/to/scratch/emacs/2022-08/test-load/
/path/to/scratch/emacs/2022-08/test-load/
├── bar.el
├── foo.el
└── init.el

0 directories, 3 files
  • bar.el

    ;;; bar.el --- Bar package -*- lexical-binding: t; -*-
    ;;; Commentary:
    ;;; Code:
    
    (provide 'bar)
    ;;; bar.el ends here
    
  • foo.el

    ;;; foo.el --- Foo package -*- lexical-binding: t; -*-
    ;;; Commentary:
    ;;; Code:
    
    (defun foo-function-toplevel () nil)
    
    (add-hook 'window-setup-hook
              (lambda ()
                (defun foo-function-after-init () nil)))
    
    (with-eval-after-load 'bar
      (defun foo-function-after-bar () nil))
    
    (provide 'foo)
    ;;; foo.el ends here
    
  • init.el

    (add-to-list 'load-path (file-name-directory load-file-name))
    (load "foo")
    (load "bar")
    
    (require 'help-fns)
    (defun find-function-library (function)
      "Return filename the FUNCTION defined."
      (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))
    
    (run-with-timer
     0.1 nil
     (lambda ()
       (switch-to-buffer "*Messages*")
       (message "foo-function-toplevel   : %S" (find-function-library 'foo-function-toplevel))
       (message "foo-function-after-init : %S" (find-function-library 'foo-function-after-init))
       (message "foo-function-after-bar  : %S" (find-function-library 'foo-function-after-bar))))
    

测试结果:

$ emacs -Q -l /path/to/scratch/emacs/2022-08/test-load/init.el -nw
For information about GNU Emacs and the GNU system, type C-h C-a.
Loading /path/to/scratch/emacs/2022-08/test-load/foo.el (source)...done
Loading /path/to/scratch/emacs/2022-08/test-load/bar.el (source)...done
foo-function-toplevel   : "/path/to/scratch/emacs/2022-08/test-load/foo.el"
foo-function-after-init : nil
foo-function-after-bar  : "/path/to/scratch/emacs/2022-08/test-load/init.el"

可以看到三个函数分指向不同的位置。其中 (with-eval-after-load ...) 中定义的函数就指向了 init.el

1 个赞

我把 symbol-file 函数里面使用 load-history 的地方加了 reverse,解决了问题。感觉就是顺序的问题,延迟定义的函数在 load-history 里面出现了两次,原有的逻辑用了第一次出现的位置,恰好就是我的配置文件。

;; (pcase-dolist (`(,file . ,elems) load-history)  ; 原来的代码
(pcase-dolist (`(,file . ,elems) (reverse load-history))

我发现我也有这个问题在 emacs 29 上,emacs 28 上没有问题,但是只有在同时使用 straightuse-package 的时候才能复现,如果使用 package.eluse-package 则一切正常。

  • init.el 使用 straightuse-package 复现
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))
(straight-use-package 'use-package)
(straight-use-package 'sudoku)
(use-package sudoku)
  • load-history (emacs 29)

  • load-history (emacs 28)

在 leaf 修过类似的

的确用 straight 可以重现这个问题,但是非 autoload 函数不受影响:

$ emacs -Q -l ~/.scratch/emacs/2022-08/test-load-history.el -nw

==> filename in load-history:
(async-start . "~/.scratch/emacs/2022-08/test-load-history.el")
(async-start . "~/.emacs.d/straight/build/async/async.elc")
(async-get . "~/.emacs.d/straight/build/async/async.elc")

==> filename in describe-function:
(async-start . "~/.scratch/emacs/2022-08/test-load-history.el")
(async-get . "~/.emacs.d/straight/build/async/async.el")
test-load-history.el
;;; Usage: /path/to/emacs -nw -Q -l /path/to/test-load-history.el
;;; Date: 2022-08-17_10.04.02
;; @topic https://emacs-china.org/t/topic/21808
(toggle-debug-on-error)

;; ---------------------------------------------------------------------------
;; straight

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

;; ---------------------------------------------------------------------------
;; config

(straight-use-package 'async)
(require 'async)

(defun print-filename-describe-function (function)
  "Print filename of FUNCTION you see in `describe-function'."
  (require 'help-fns)
  (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))))
    (print (cons function file-name))))

(defun print-filename-load-history (function)
  "Print filename of FUNCTION you see in `load-history'."
  (mapc (lambda (it)
          (when (member (cons 'defun function) (cdr it))
            (print (cons function (car it)))))
        load-history))

(add-hook 'window-setup-hook
          (lambda ()
            (switch-to-buffer "*Messages*")

            (message "==> filename in load-history:")
            (print-filename-load-history 'async-start)
            (print-filename-load-history 'async-get)

            (message "==> filename in describe-function:")
            (print-filename-describe-function 'async-start)
            (print-filename-describe-function 'async-get)))

;;; test-load-history.el ends here
1 个赞

这么看来问题只是关于 straight,跟 use-package 没啥关系应该。

也可以说跟 Emacs 有关。

emacformacos.com 上可以下载到的最后一个没问题的版本是:

Emacs-2022-01-31_00-09-05-9a56b4e-universal.dmg

第一个有问题的版本是:

Emacs-2022-02-01_00-09-12-30ebb54-universal.dmg

这一天当中有 34 个提交[1],可以二分法编译试试,电脑性能好的话,很快就定位到。

[1] https://emacsformacosx.com/download/emacs-builds/Emacs-2022-02-01_00-09-12-30ebb54410d18a4f782fe39d21c1941f9852ec8f.changes