]> git.eshelyaron.com Git - emacs.git/commitdiff
Make edebug-step-in work on generic methods (Bug#22294)
authorGemini Lasswell <gazally@runbox.com>
Sat, 13 May 2017 18:35:49 +0000 (11:35 -0700)
committerDmitry Gutov <dgutov@yandex.ru>
Sun, 14 May 2017 20:32:27 +0000 (23:32 +0300)
* lisp/emacs-lisp/edebug.el (edebug-match-cl-generic-method-args):
New function to implement the edebug-form-spec property of
the symbol cl-generic-method-args.
(edebug-instrument-function): If the function is a generic
function, find and instrument all of its methods. Return a list
instead of a single symbol.
(edebug-instrument-callee): Now returns a list. Update docstring.
(edebug-step-in): Handle the list returned by edebug-instrument-callee.
* lisp/emacs-lisp/cl-generic.el (cl-defmethod): Use name and
cl-generic-method-args in its Edebug spec.
* lisp/emacs-lisp/eieio-compat.el (defmethod): Use name and
cl-generic-method-args in its Edebug spec.
* lisp/subr.el (method-files): New function.
* test/lisp/subr-tests.el (subr-tests--method-files--finds-methods)
(subr-tests--method-files--nonexistent-methods): New tests.

lisp/emacs-lisp/cl-generic.el
lisp/emacs-lisp/edebug.el
lisp/emacs-lisp/eieio-compat.el
lisp/subr.el
test/lisp/subr-tests.el

index 068f4fb0c846b1048d82c3002d4b249d333df2d1..c64376b940f703f6a13811357ab9b7cde9845386 100644 (file)
@@ -413,12 +413,12 @@ The set of acceptable TYPEs (also called \"specializers\") is defined
   (declare (doc-string 3) (indent 2)
            (debug
             (&define                    ; this means we are defining something
-             [&or symbolp ("setf" symbolp)]
+             [&or name ("setf" name :name setf)]
              ;; ^^ This is the methods symbol
              [ &rest atom ]         ; Multiple qualifiers are allowed.
                                     ; Like in CLOS spec, we support
                                     ; any non-list values.
-             listp                      ; arguments
+             cl-generic-method-args     ; arguments
              [ &optional stringp ]      ; documentation string
              def-body)))                ; part to be debugged
   (let ((qualifiers nil))
index 4116e31d0a957557bd85c113e6a11e99e808c4b9..65e30f86778ed6996755a10560d84e7e5cd606d4 100644 (file)
@@ -1607,6 +1607,7 @@ expressions; a `progn' form will be returned enclosing these forms."
                ;; Less frequently used:
                ;; (function . edebug-match-function)
                (lambda-expr . edebug-match-lambda-expr)
+                (cl-generic-method-args . edebug-match-cl-generic-method-args)
                (&not . edebug-match-&not)
                (&key . edebug-match-&key)
                (place . edebug-match-place)
@@ -1900,6 +1901,16 @@ expressions; a `progn' form will be returned enclosing these forms."
          spec))
   nil)
 
+(defun edebug-match-cl-generic-method-args (cursor)
+  (let ((args (edebug-top-element-required cursor "Expected arguments")))
+    (if (not (consp args))
+        (edebug-no-match cursor "List expected"))
+    ;; Append the arguments to edebug-def-name.
+    (setq edebug-def-name
+          (intern (format "%s %s" edebug-def-name args)))
+    (edebug-move-cursor cursor)
+    (list args)))
+
 (defun edebug-match-arg (cursor)
   ;; set the def-args bound in edebug-defining-form
   (let ((edebug-arg (edebug-top-element-required cursor "Expected arg")))
@@ -3186,8 +3197,11 @@ go to the end of the last sexp, or if that is the same point, then step."
         )))))
 
 (defun edebug-instrument-function (func)
-  ;; Func should be a function symbol.
-  ;; Return the function symbol, or nil if not instrumented.
+  "Instrument the function or generic method FUNC.
+Return the list of function symbols which were instrumented.
+This may be simply (FUNC) for a normal function, or a list of
+generated symbols for methods.  If a function or method to
+instrument cannot be found, signal an error."
   (let ((func-marker (get func 'edebug)))
     (cond
      ((and (markerp func-marker) (marker-buffer func-marker))
@@ -3195,10 +3209,24 @@ go to the end of the last sexp, or if that is the same point, then step."
       (with-current-buffer (marker-buffer func-marker)
        (goto-char func-marker)
        (edebug-eval-top-level-form)
-       func))
+        (list func)))
      ((consp func-marker)
       (message "%s is already instrumented." func)
-      func)
+      (list func))
+     ((get func 'cl--generic)
+      (let ((method-defs (method-files func))
+            symbols)
+        (unless method-defs
+          (error "Could not find any method definitions for %s" func))
+        (pcase-dolist (`(,file . ,spec) method-defs)
+          (let* ((loc (find-function-search-for-symbol spec 'cl-defmethod file)))
+            (unless (cdr loc)
+              (error "Could not find the definition for %s in its file" spec))
+            (with-current-buffer (car loc)
+              (goto-char (cdr loc))
+              (edebug-eval-top-level-form)
+              (push (edebug-form-data-symbol) symbols))))
+        symbols))
      (t
       (let ((loc (find-function-noselect func t)))
        (unless (cdr loc)
@@ -3206,13 +3234,16 @@ go to the end of the last sexp, or if that is the same point, then step."
        (with-current-buffer (car loc)
          (goto-char (cdr loc))
          (edebug-eval-top-level-form)
-         func))))))
+          (list func)))))))
 
 (defun edebug-instrument-callee ()
   "Instrument the definition of the function or macro about to be called.
 Do this when stopped before the form or it will be too late.
 One side effect of using this command is that the next time the
-function or macro is called, Edebug will be called there as well."
+function or macro is called, Edebug will be called there as well.
+If the callee is a generic function, Edebug will instrument all
+the methods, not just the one which is about to be called.  Return
+the list of symbols which were instrumented."
   (interactive)
   (if (not (looking-at "("))
       (error "You must be before a list form")
@@ -3227,15 +3258,15 @@ function or macro is called, Edebug will be called there as well."
 
 
 (defun edebug-step-in ()
-  "Step into the definition of the function or macro about to be called.
+  "Step into the definition of the function, macro or method about to be called.
 This first does `edebug-instrument-callee' to ensure that it is
 instrumented.  Then it does `edebug-on-entry' and switches to `go' mode."
   (interactive)
-  (let ((func (edebug-instrument-callee)))
-    (if func
+  (let ((funcs (edebug-instrument-callee)))
+    (if funcs
        (progn
-         (edebug-on-entry func 'temp)
-         (edebug-go-mode nil)))))
+          (mapc (lambda (func) (edebug-on-entry func 'temp)) funcs)
+          (edebug-go-mode nil)))))
 
 (defun edebug-on-entry (function &optional flag)
   "Cause Edebug to stop when FUNCTION is called.
index fe65ae02623c6c4b249d2f49d984bdc9270387b0..e6e6d118709c8a51a95f171aeae08b21865bfd85 100644 (file)
@@ -105,10 +105,10 @@ Summary:
   (declare (doc-string 3) (obsolete cl-defmethod "25.1")
            (debug
             (&define                    ; this means we are defining something
-             [&or symbolp ("setf" symbolp)]
+             [&or name ("setf" name :name setf)]
              ;; ^^ This is the methods symbol
              [ &optional symbolp ]                ; this is key :before etc
-             listp                                ; arguments
+             cl-generic-method-args               ; arguments
              [ &optional stringp ]                ; documentation string
              def-body                             ; part to be debugged
              )))
index 02e7993223397ddc9d645e30e970543337e7a49d..8d5d2a779c69e40915e3b0640597354336f11916 100644 (file)
@@ -2026,6 +2026,25 @@ definition, variable definition, or face definition only."
        (setq files (cdr files)))
       file)))
 
+(defun method-files (method)
+  "Return a list of files where METHOD is defined by `cl-defmethod'.
+The list will have entries of the form (FILE . (METHOD ...))
+where (METHOD ...) contains the qualifiers and specializers of
+the method and is a suitable argument for
+`find-function-search-for-symbol'.  Filenames are absolute."
+  (let ((files load-history)
+        result)
+    (while files
+      (let ((defs (cdr (car files))))
+        (while defs
+          (let ((def (car defs)))
+            (if (and (eq (car-safe def) 'cl-defmethod)
+                     (eq (cadr def) method))
+                (push (cons (car (car files)) (cdr def)) result)))
+          (setq defs (cdr defs))))
+      (setq files (cdr files)))
+    result))
+
 (defun locate-library (library &optional nosuffix path interactive-call)
   "Show the precise file name of Emacs library LIBRARY.
 LIBRARY should be a relative file name of the library, a string.
index 0d243cc5d8c26367c9fa28b852ffe25fca5e9014..8fa258d12ed3b9add8c7eb3cc109c165f7e5d299 100644 (file)
@@ -291,5 +291,29 @@ cf. Bug#25477."
   (should-error (eval '(dolist "foo") t)
                 :type 'wrong-type-argument))
 
+(require 'cl-generic)
+(cl-defgeneric subr-tests--generic (x))
+(cl-defmethod subr-tests--generic ((x string))
+  (message "%s is a string" x))
+(cl-defmethod subr-tests--generic ((x integer))
+  (message "%s is a number" x))
+(cl-defgeneric subr-tests--generic-without-methods (x y))
+(defvar subr-tests--this-file (or load-file-name buffer-file-name))
+
+(ert-deftest subr-tests--method-files--finds-methods ()
+  "`method-files' returns a list of files and methods for a generic function."
+  (let ((retval (method-files 'subr-tests--generic)))
+    (should (equal (length retval) 2))
+    (mapc (lambda (x)
+            (should (equal (car x) subr-tests--this-file))
+            (should (equal (cadr x) 'subr-tests--generic)))
+          retval)
+    (should-not (equal (nth 0 retval) (nth 1 retval)))))
+
+(ert-deftest subr-tests--method-files--nonexistent-methods ()
+  "`method-files' returns nil if asked to find a method which doesn't exist."
+  (should-not (method-files 'subr-tests--undefined-generic))
+  (should-not (method-files 'subr-tests--generic-without-methods)))
+
 (provide 'subr-tests)
 ;;; subr-tests.el ends here