]> git.eshelyaron.com Git - emacs.git/commitdiff
scope.el: Improve entry point and nested backquote handling
authorEshel Yaron <me@eshelyaron.com>
Sun, 11 Aug 2024 09:52:31 +0000 (11:52 +0200)
committerEshel Yaron <me@eshelyaron.com>
Sun, 11 Aug 2024 09:52:31 +0000 (11:52 +0200)
lisp/emacs-lisp/scope.el
lisp/progmodes/elisp-mode.el
lisp/progmodes/refactor-elisp.el
lisp/progmodes/refactor.el
test/lisp/emacs-lisp/scope-tests.el

index 423effea49b5518da56d7cbb7ab848a167db2618..680963faa375ee75dcc2d6eaf7f1fa7bba53dd40 100644 (file)
@@ -26,8 +26,6 @@
 
 (eval-when-compile (require 'cl-lib))
 
-(defvar scope-flet-list nil)
-
 (defun scope-s (local sym)
   (let* ((beg (symbol-with-pos-pos sym))
          (bare (bare-symbol sym))
                     fun))
    (scope-n local body)))
 
-(defun scope-backquote (local elements)
+(defun scope-backquote (depth local elements)
   (cond
+   ((zerop depth) (scope-n local elements))
    ((consp elements)
     (cond
      ((memq (car elements) '(\, \,@))
-      (scope-1 local (cadr elements)))
-     (t (nconc (scope-backquote local (car elements))
-               (scope-backquote local (cdr elements))))))
+      (scope-backquote (1- depth) local (cdr elements)))
+     ((eq (car elements) '\`)
+      (scope-backquote (1+ depth) local (cdr elements)))
+     (t (nconc (scope-backquote depth local (car elements))
+               (scope-backquote depth local (cdr elements))))))
    ((vectorp elements)
-    (scope-backquote local (append elements nil)))))
+    (scope-backquote depth local (append elements nil)))))
+
+(defvar scope-flet-list nil)
 
 (defun scope-flet (local defs body)
   (if defs
   (while (keywordp (car body)) (setq body (cddr body)))
   (scope-n local body))
 
+(defun scope-letrec (local binders body)
+  (if binders
+      (let* ((binder (car binders))
+             (sym (car binder))
+             (bare (bare-symbol sym))
+             (beg (symbol-with-pos-pos sym))
+             (l (cons (cons bare beg) local))
+             (form (cadr binder)))
+        (cons
+         (list beg (length (symbol-name bare)) beg)
+         (nconc (scope-1 l form)
+                (scope-letrec l (cdr binders) body))))
+    (scope-n local body)))
+
 (defun scope-f (local f)
   "Return function that scope-analyzes arguments of F in context LOCAL."
   (cond
          ((eq (get bare 'edebug-form-spec) t)
           (apply-partially #'scope-n local))
          ((memq bare '( setf with-memoization cl-assert cl-incf cl-decf
-                        eval-when-compile eval-and-compile with-eval-after-load))
+                        eval-when-compile eval-and-compile with-eval-after-load
+                        ;; We could recognize contant symbols bindings
+                        ;; in `cl-progv', but it is not really worth the
+                        ;; trouble since this macro is specifically
+                        ;; intended for computing bindings at run time.
+                        cl-progv))
           (apply-partially #'scope-n local))
          ((memq bare '( defun defmacro defsubst define-inline))
           (lambda (forms) (scope-defun local (car forms) (cadr forms) (cddr forms))))
           (lambda (forms) (scope-push local (car forms) (cadr forms))))
          ((memq bare '(pop oref))
           (lambda (forms) (scope-1 local (car forms))))
+         ((memq bare '(letrec))
+          (lambda (forms) (scope-letrec local (car forms) (cdr forms))))
          ((memq bare '(cl-flet))
           (lambda (forms) (scope-flet local (car forms) (cdr forms))))
          ((memq bare '(cl-labels))
          ((memq bare '( define-minor-mode))
           (lambda (forms) (scope-define-minor local (car forms) (cadr forms) (cddr forms))))
          ((memq bare '(inline-quote))
-          (lambda (forms) (scope-backquote local (car forms))))
+          (lambda (forms) (scope-backquote local (car forms))))
          ((memq bare '(inline-letevals))
           (lambda (forms) (scope-let local (car forms) (cdr forms))))
          ((memq bare '(with-suppressed-warnings))
    ;; expands into a symbol but does not annotate with a position.
    ((symbolp f)
     (cond
-     ((eq f '\`) (lambda (forms) (scope-backquote local (car forms))))
+     ((eq f '\`) (lambda (forms) (scope-backquote local (car forms))))
      ((eq f 'function) (lambda (forms) (scope-function local (car forms))))
      (t #'ignore)))
    (t #'ignore)))
 (defun scope-n (local body) (mapcan (apply-partially #'scope-1 local) body))
 
 ;;;###autoload
-(defun scope (form)
-  "Return bindings graph in FORM.
-
-FORM should contain positioned symbols, see `read-positioning-symbols'.
-
-The graph is a list of elements (OCCURENCE LEN BINDING): OCCURENCE is a
-buffer position where a symbol of length LEN occurs, which is bound by
-another occurence of the same symbol that starts at position BINDING.
-If the symbol at OCCURENCE is not lexically bound, then BINDING is nil."
-  (scope-1 nil form))
+(defun scope (&optional stream)
+  "Read and scope-analyze code from STREAM.
+
+Return a bindings graph associating symbols with their binders.  It is a
+list of elements (OCCURRENCE LEN BINDING) where OCCURRENCE is a buffer
+position where a symbol of length LEN occurs, which is bound by another
+occurrence of the same symbol that starts at position BINDING.  If
+OCCURRENCE is itself a binding occurrence, then BINDING and OCCURRENCE
+are equal.  If OCCURRENCE is not lexically bound, then BINDING is nil."
+  (scope-1 nil (read-positioning-symbols stream)))
 
 (provide 'scope)
 ;;; scope.el ends here
index dae613426f67ff92e06bd82583dedb95975ae71c..1a5861d80dd7c45704bd3ce05aa085719e10b682 100644 (file)
@@ -336,10 +336,10 @@ happens in interactive invocations."
 (defun elisp-highlight-variable (pos)
   (save-excursion
     (goto-char pos)
-    (let* ((all (scope (save-excursion
-                         (goto-char pos)
-                         (beginning-of-defun)
-                         (read-positioning-symbols (current-buffer)))))
+    (let* ((all (save-excursion
+                  (goto-char pos)
+                  (beginning-of-defun)
+                  (scope (current-buffer))))
            (dec (seq-some
                  (pcase-lambda (`(,beg ,len ,bin))
                    (when (<= beg pos (+ beg len)) bin))
@@ -374,7 +374,9 @@ happens in interactive invocations."
             (goto-char beg)
             (while (< (point) end)
               (pcase-dolist (`(,sym ,len ,bin)
-                             (scope (read-positioning-symbols (current-buffer))))
+                             (condition-case nil
+                                 (scope (current-buffer))
+                               (end-of-file nil)))
                 (if (null bin)
                     (font-lock-append-text-property sym (+ sym len) 'face 'elisp-free-variable)
                   (font-lock-append-text-property sym (+ sym len) 'face (if (= sym bin)
@@ -1112,10 +1114,10 @@ namespace but with lower confidence."
          (dec (seq-some
                (pcase-lambda (`(,beg ,len ,dec))
                  (when (<= beg pos (+ beg len)) dec))
-               (scope (save-excursion
-                        (goto-char pos)
-                        (beginning-of-defun)
-                        (read-positioning-symbols (current-buffer)))))))
+               (save-excursion
+                 (goto-char pos)
+                 (beginning-of-defun)
+                 (scope (current-buffer))))))
     (if dec (list (xref-make "lexical binding"
                              (xref-make-buffer-location (current-buffer) dec)))
       (require 'find-func)
@@ -1133,10 +1135,10 @@ namespace but with lower confidence."
 
 (cl-defmethod xref-backend-references :around ((backend (eql 'elisp)) identifier)
   (let* ((pos (get-text-property 0 'pos identifier))
-         (all (scope (save-excursion
-                       (goto-char pos)
-                       (beginning-of-defun)
-                       (read-positioning-symbols (current-buffer)))))
+         (all (save-excursion
+                (goto-char pos)
+                (beginning-of-defun)
+                (scope (current-buffer))))
          (dec (seq-some
                (pcase-lambda (`(,beg ,len ,bin))
                  (when (<= beg pos (+ beg len)) bin))
index fb6310d2b5ccd94fd47792d79080112bab5c0600..fa5756572dbb0aa01c67fb0991006680d9e77ced 100644 (file)
 (defun elisp-refactor-backend () '(elisp rename))
 
 (cl-defmethod refactor-backend-read-scoped-identifier ((_backend (eql elisp)))
-  (let ((all (scope (save-excursion
-                      (beginning-of-defun)
-                      (read-positioning-symbols (current-buffer))))))
+  (let ((all (save-excursion
+               (beginning-of-defun)
+               (scope (current-buffer)))))
     (seq-some
      (pcase-lambda (`(,beg ,len ,bin))
        (and bin (<= beg (point) (+ beg len))
-            (list (buffer-substring-no-properties beg (+ beg len)))))
+            (list (propertize (buffer-substring-no-properties beg (+ beg len))
+                              'pos beg))))
      all)))
 
 (cl-defmethod refactor-backend-rename-edits ((_backend (eql elisp)) _old new (_scope (eql nil)))
-  (let* ((all (scope (save-excursion
-                       (beginning-of-defun)
-                       (read-positioning-symbols (current-buffer)))))
+  (let* ((all (save-excursion
+                (beginning-of-defun)
+                (scope (current-buffer))))
          (dec (seq-some
                (pcase-lambda (`(,beg ,len ,bin))
                  (when (<= beg (point) (+ beg len)) bin))
                 (list beg (+ beg len) new)))
             all)))))
 
+(cl-defmethod refactor-backend-rename-highlight-regions
+  ((_backend (eql elisp)) old (_scope (eql nil)))
+  (when-let* ((pos (get-text-property 0 'pos old))
+              (all (save-excursion
+                     (goto-char pos)
+                     (beginning-of-defun)
+                     (scope (current-buffer))))
+              (dec (seq-some
+                    (pcase-lambda (`(,beg ,len ,bin))
+                      (when (<= beg pos (+ beg len)) bin))
+                    all)))
+    (mapcar (pcase-lambda (`(,beg ,len ,bin))
+              (when (equal bin dec)
+                (cons beg (+ beg len))))
+            all)))
+
 (provide 'refactor-elisp)
 ;;; refactor-elisp.el ends here
index 560e98feff249056386baf5aedb23aa0988a9f84..e74ed16d680284988a8db2f7721c1e9c662ebc53 100644 (file)
@@ -159,15 +159,23 @@ is already in use, return a string to display as feedback to the user.
 Otherwise, if the replacement is valid, return nil."
   nil)
 
-(cl-defgeneric refactor-backend-read-replacement (backend old scope)
-  "Read a replacement for identifier OLD across SCOPE using BACKEND."
-  (let ((case-fold-search nil))
+(cl-defgeneric refactor-backend-rename-highlight-regions (_backend old _scope)
+  "Return regions to highlight while prompting for replacement for OLD."
+  (let ((regions nil)
+        (case-fold-search nil))
     (save-excursion
       (goto-char (point-min))
       (while (search-forward old nil t)
-        (let ((ov (make-overlay (match-beginning 0) (match-end 0))))
-          (overlay-put ov 'refactor-rename-old t)
-          (overlay-put ov 'face 'lazy-highlight)))))
+        (push (cons (match-beginning 0) (match-end 0)) regions)))
+    regions))
+
+(cl-defgeneric refactor-backend-read-replacement (backend old scope)
+  "Read a replacement for identifier OLD across SCOPE using BACKEND."
+  (pcase-dolist (`(,beg . ,end)
+                 (refactor-backend-rename-highlight-regions backend old scope))
+    (let ((ov (make-overlay beg end)))
+      (overlay-put ov 'refactor-rename-old t)
+      (overlay-put ov 'face 'lazy-highlight)))
   (unwind-protect
       (let ((new nil)
             (invalid nil))
index c45de389af80224ce41c310706de0748a1457403..030e05d0335279cbff48efa920ed9d1d2712d988 100644 (file)
 (require 'ert)
 
 (ert-deftest scope-test-1 ()
-  (let* ((str "
+  (should (equal '((13 3 13)
+                   (17 3 17)
+                   (32 3 32)
+                   (36 3 17)
+                   (51 3 51)
+                   (55 3 32)
+                   (76 3 13)
+                   (80 4 nil)
+                   (85 3 51)
+                   (104 3 13)
+                   (108 3 51)
+                   (125 3 17))
+                 (scope "
 (defun foo (bar baz)
   (let* ((baz baz)
          (baz baz))
     (when (and bar spam baz)
       (ignore bar baz)))
-  (ignore baz))")
-         (form (read-positioning-symbols str)))
-    (should (equal (scope form)
-                   '((13 3 13)
-                     (17 3 17)
-                     (32 3 32)
-                     (36 3 17)
-                     (51 3 51)
-                     (55 3 32)
-                     (76 3 13)
-                     (80 4 nil)
-                     (85 3 51)
-                     (104 3 13)
-                     (108 3 51)
-                     (125 3 17))))))
+  (ignore baz))"))))
 
 (ert-deftest scope-test-2 ()
-  (let* ((str "
+  (should (equal '((110 11 110)
+                   (197 6 197)
+                   (236 6 197)
+                   (257 2 257)
+                   (263 3 263)
+                   (287 2 287)
+                   (290 3 263)
+                   (313 2 257)
+                   (327 2 287)
+                   (330 11 110)
+                   (353 11 110))
+                 (scope "
 (defun refactor-backends ()
   \"Return alist of refactor operations and backends that support them.\"
   (let ((op-be-alist nil))
          (`(,be . ,ops)
           (dolist (op ops)
             (push be (alist-get op op-be-alist)))))))
-    op-be-alist))")
-         (form (read-positioning-symbols str)))
-    (should (equal (scope form)
-                   '((110 11 110)
-                     (197 6 197)
-                     (236 6 197)
-                     (257 2 257)
-                     (263 3 263)
-                     (287 2 287)
-                     (290 3 263)
-                     (313 2 257)
-                     (327 2 287)
-                     (330 11 110)
-                     (353 11 110))))))
+    op-be-alist))"))))
+
+(ert-deftest scope-test-3 ()
+  (should (equal '((45 3 45)            ;env
+                   (55 4 55)            ;body
+                   (136 4 136)          ;syms
+                   (172 4 172)          ;vals
+                   (212 4 136)
+                   (218 4 172)
+                   (258 3 45)
+                   (272 4 136)
+                   (287 4 172)
+                   (312 4 136)
+                   (318 4 172)
+                   (334 4 55))
+                 (scope "
+(defmacro erc--with-entrypoint-environment (env &rest body)
+  \"Run BODY with bindings from ENV alist.\"
+  (declare (indent 1))
+  (let ((syms (make-symbol \"syms\"))
+        (vals (make-symbol \"vals\")))
+    `(let (,syms ,vals)
+       (pcase-dolist (`(,k . ,v) ,env) (push k ,syms) (push v ,vals))
+       (cl-progv ,syms ,vals
+         ,@body))))"))))
 
 ;;; scope-tests.el ends here