Help find-function find methods defined inside macros

* doc/lispref/functions.texi (Finding Definitions): Document the
expanded definition-finding extension mechanism.
* etc/NEWS: Briefly describe the new feature.
* lisp/emacs-lisp/cl-generic.el
(cl--generic-find-defgeneric-regexp): Use defconst now that we
no longer have purespace.
(cl--generic-search-method-make-form-matcher): New function.
* lisp/emacs-lisp/find-func.el (find-function-regexp-alist)
(find-function-search-for-symbol): Parse out the new
factory function.
(find-function--search-by-expanding-macros): Try using it when
searching for definitions by expanding macros.
This commit is contained in:
Daniel Colascione 2025-03-27 16:04:51 -04:00
parent 59fd8c26be
commit 364c3dbc12
4 changed files with 83 additions and 28 deletions

View file

@ -849,9 +849,16 @@ The alist @code{find-function-regexp-alist} associates object types with
a regexp or function that finds the definition of that object in its
source file. Each element's car is a symbol the describes the type of
object, or @code{nil} to identify functions defined with @code{defun}.
Each element's cdr is a symbol: either the value of that symbol is a
string interpreted as a regexp, or that symbol names a function that can
find the definition.
Each element's cdr can be:
@itemize
@item
A symbol whose value is a string interpreted as a regexp
@item
A symbol naming a function that can find the definition
@item
A cons cell where the car is a regexp (or function that returns one) and the cdr is a function that creates a matcher for macroexpanded forms
@end itemize
A regexp string is actually a format string, and @code{%s} will be
substituted with the name of the symbol we are looking for.
@ -859,6 +866,11 @@ substituted with the name of the symbol we are looking for.
A function will be called with one argument, the (symbol for) the object
we are searching for.
The form-matcher function in a cons cell value is called with one argument (the
symbol being sought) and should return a function that takes a form and returns
non-nil if the form defines the sought symbol. This is useful for finding
definitions that are created by macro expansion.
@cindex @code{definition-name} (symbol property)
If the function to be found is defined by a macro, it may be hard for
Emacs to find the definition site in the source code. A macro call may

View file

@ -75,7 +75,8 @@ the 'standard-display-table's extra slots with Unicode characters.
Please see the documentation of that function to see which slots of the
display table it changes.
+++
---
** 'find-function' can now find cl-defmethod invocations hidden inside macros.
** Child frames are now supported on TTY frames.
This supports use-cases like Posframe, Corfu, and child frames acting
like tooltips.

View file

@ -1082,13 +1082,36 @@ MET-NAME is as returned by `cl--generic-load-hist-format'."
nil t)
(re-search-forward base-re nil t))))
;; WORKAROUND: This can't be a defconst due to bug#21237.
(defvar cl--generic-find-defgeneric-regexp "(\\(?:cl-\\)?defgeneric[ \t]+%s\\_>")
(defun cl--generic-search-method-make-form-matcher (met-name)
(let ((name (car met-name))
(qualifiers (cadr met-name))
(specializers (cddr met-name)))
(lambda (form)
(pcase form
(`(cl-generic-define-method
(function ,(pred (eq name)))
(quote ,(and (pred listp) m-qualifiers))
(quote ,(and (pred listp) m-args))
,_call-con
,_function)
(ignore-errors
(let* ((m-spec-args (car (cl--generic-split-args m-args)))
(m-specializers
(mapcar (lambda (spec-arg)
(if (eq '&context (car-safe (car spec-arg)))
spec-arg (cdr spec-arg)))
m-spec-args)))
(and (equal qualifiers m-qualifiers)
(equal specializers m-specializers)))))))))
(defconst cl--generic-find-defgeneric-regexp "(\\(?:cl-\\)?defgeneric[ \t]+%s\\_>")
(with-eval-after-load 'find-func
(defvar find-function-regexp-alist)
(add-to-list 'find-function-regexp-alist
`(cl-defmethod . ,#'cl--generic-search-method))
`(cl-defmethod
. (,#'cl--generic-search-method
. #'cl--generic-search-method-make-form-matcher)))
(add-to-list 'find-function-regexp-alist
'(cl-defgeneric . cl--generic-find-defgeneric-regexp)))

View file

@ -144,6 +144,16 @@ Instead of regexp variable, types can be mapped to functions as well,
in which case the function is called with one argument (the object
we're looking for) and it should search for it.
A value can also be a cons (REGEX . EXPANDED-FORM-MATCHER-FACTORY).
REGEX is as above; EXPANDED-FORM-MATCHER-FACTORY is a function of one
argument, the same as we'd pass to a REGEX function, that returns
another function of one argument that returns true if we're looking at a
macroexpanded form that defines what we're looking for. If you want to
use EXPANDED-FORM-MATCHER-FACTORY exclusively, you can set REGEX to a
never-match regex and force the fallback to
EXPANDED-FORM-MATCHER-FACTORY. The buffer to search is current during
the call to EXPANDED-FORM-MATCHER-FACTORY.
Symbols can have their own version of this alist on
the property `find-function-type-alist'.
See the function `find-function-update-type-alist'.")
@ -434,7 +444,13 @@ The search is done in the source for library LIBRARY."
(regexp-symbol
(or (and (symbolp symbol)
(alist-get type (get symbol 'find-function-type-alist)))
(alist-get type find-function-regexp-alist))))
(alist-get type find-function-regexp-alist)))
(form-matcher-factory
(and (functionp (cdr-safe regexp-symbol))
(cdr regexp-symbol)))
(regexp-symbol (if form-matcher-factory
(car regexp-symbol)
regexp-symbol)))
(with-current-buffer (find-file-noselect filename)
(let ((regexp (if (functionp regexp-symbol) regexp-symbol
(format (symbol-value regexp-symbol)
@ -474,7 +490,8 @@ The search is done in the source for library LIBRARY."
;; expands macros until it finds the symbol.
(cons (current-buffer)
(find-function--search-by-expanding-macros
(current-buffer) symbol type))))))))))
(current-buffer) symbol type
form-matcher-factory))))))))))
;;;###autoload
(defun find-function-update-type-alist (symbol type variable)
@ -506,19 +523,13 @@ Return t if any PRED returns t."
(find-function--any-subform-p left-child pred)
(find-function--any-subform-p right-child pred))))))
(defun find-function--search-by-expanding-macros (buf symbol type)
(defun find-function--search-by-expanding-macros
(buf symbol type matcher-factory)
"Expand macros in BUF to search for the definition of SYMBOL of TYPE."
(catch 'found
(with-current-buffer buf
(save-excursion
(goto-char (point-min))
(condition-case nil
(while t
(let ((form (read (current-buffer)))
(expected-symbol-p
(lambda (form)
(cond
((null type)
(with-current-buffer buf
(when-let* ((expected-symbol-p
(cond ((null type)
(lambda (form)
;; Check if a given form is a `defalias' to
;; SYM, the function name we are searching
;; for. All functions in Emacs Lisp
@ -526,20 +537,28 @@ Return t if any PRED returns t."
;; after several steps of macroexpansion.
(and (eq (car-safe form) 'defalias)
(equal (car-safe (cdr form))
`(quote ,symbol))))
((eq type 'defvar)
`(quote ,symbol)))))
((eq type 'defvar)
(lambda (form)
;; Variables generated by macros ultimately
;; expand to `defvar'.
(and (eq (car-safe form) 'defvar)
(eq (car-safe (cdr form)) symbol)))
(t nil)))))
(eq (car-safe (cdr form)) symbol))))
(matcher-factory
(funcall matcher-factory symbol)))))
(catch 'found
(save-excursion
(goto-char (point-min))
(condition-case nil
(while t
(when (find-function--any-subform-p
(find-function--try-macroexpand form)
(find-function--try-macroexpand
(read (current-buffer)))
expected-symbol-p)
;; We want to return the location at the beginning
;; of the macro, so move back one sexp.
(throw 'found (progn (backward-sexp) (point))))))
(end-of-file nil))))))
(throw 'found (progn (backward-sexp) (point)))))
(end-of-file nil)))))))
(defun find-function-library (function &optional lisp-only verbose)
"Return the pair (ORIG-FUNCTION . LIBRARY) for FUNCTION.