]> git.eshelyaron.com Git - emacs.git/commitdiff
Preserve source position of macro calls in macro expansions
authorAlan Mackenzie <acm@muc.de>
Sun, 13 Jul 2025 20:28:51 +0000 (20:28 +0000)
committerEshel Yaron <me@eshelyaron.com>
Thu, 24 Jul 2025 08:47:08 +0000 (10:47 +0200)
This allows the byte compiler to give correct positions, those
of the invoking forms, when an error or warning is caused by
the innards of the invoked macros.

This fixes bug#73725 and bug#73746.

* lisp/emacs-lisp/macroexp.el (macroexp--posify-form-1)
(macroexp--posify-form): New functions.
(macroexp-preserve-posification): New macro.
(macroexp--compiler-macro, macroexp-macroexpand): Use the new
macro to preserve a calling form's position.

* lisp/emacs-lisp/byte-opt.el (byte-optimize-form): Use the new
macro to preserve source positions.

* test/lisp/emacs-lisp/bytecomp-resources/bad-error-position.el
* test/lisp/emacs-lisp/bytecomp-resources/bad-error-position-2.el:
New test files.
* test/lisp/emacs-lisp/bytecomp-tests.el: Two new tests using
the new test files.

(cherry picked from commit c44903b0118357336cca964bd210835ca4881e87)

lisp/emacs-lisp/byte-opt.el
lisp/emacs-lisp/macroexp.el
test/lisp/emacs-lisp/bytecomp-resources/macro-warning-position-2.el [new file with mode: 0644]
test/lisp/emacs-lisp/bytecomp-resources/macro-warning-position.el [new file with mode: 0644]
test/lisp/emacs-lisp/bytecomp-tests.el

index 83909c300a57129f8495cea845467b64d0c7098c..30b4d8b4baff6b91f4e54e6daaa96465e6f30b47 100644 (file)
@@ -521,7 +521,12 @@ There can be multiple entries for the same NAME if it has several aliases.")
   (while
       (progn
         ;; First, optimize all sub-forms of this one.
-        (setq form (byte-optimize-form-code-walker form for-effect))
+        ;; `byte-optimize-form-code-walker' fails to preserve any
+        ;; position on `form' in enough separate places that we invoke
+        ;; `macroexp-preserve-posification' here for source code economy.
+        (setq form
+              (macroexp-preserve-posification
+                  form (byte-optimize-form-code-walker form for-effect)))
 
         ;; If a form-specific optimizer is available, run it and start over
         ;; until a fixpoint has been reached.
@@ -530,7 +535,8 @@ There can be multiple entries for the same NAME if it has several aliases.")
              (let ((opt (byte-opt--fget (car form) 'byte-optimizer)))
                (and opt
                     (let ((old form)
-                          (new (funcall opt form)))
+                          (new (macroexp-preserve-posification
+                                   form (funcall opt form))))
                      (byte-compile-log "  %s\t==>\t%s" old new)
                       (setq form new)
                       (not (eq new old))))))))
index acd2b6f0b0500be1e32bd21b16dff4bcc9297b53..2a66a083d12bc2c183848dda9f5b77506747153a 100644 (file)
@@ -122,7 +122,9 @@ case return FORM unchanged."
   (if macroexp-inhibit-compiler-macros
       form
     (condition-case-unless-debug err
-        (apply handler form (cdr form))
+        (macroexp-preserve-posification
+             form
+          (apply handler form (cdr form)))
       (error
        (message "Warning: Optimization failure for %S: Handler: %S\n%S"
                 (car form) handler err)
@@ -238,22 +240,101 @@ It should normally be a symbol with position and it defaults to FORM."
                 form))))))))
    (t form)))
 
+(defun macroexp--posify-form-1 (form call-pos depth)
+  "The recursive part of `macroexp--posify-form'.
+It modifies a single symbol to a symbol with position, or does nothing.
+FORM and CALL-POS are as in that function.  DEPTH is a small integer,
+decremented at each recursive call, to prevent infinite recursion.
+
+Return the form with a symbol with position in the canonical position
+for that form, either the one that was already there or CALL-POS; return
+nil if this isn't possible.
+"
+  (let (new-form)
+    (cond
+     ((zerop depth) nil)
+     ((and (consp form)
+           (symbolp (car form))
+           (car form))
+      (unless (symbol-with-pos-p (car form))
+        (setcar form (position-symbol (car form) call-pos)))
+      form)
+     ((consp form)
+      (or (when (setq new-form (macroexp--posify-form-1
+                                (car form) call-pos (1- depth)))
+            (setcar form new-form)
+            form)
+          (when (setq new-form (macroexp--posify-form-1
+                                (cdr form) call-pos (1- depth)))
+            (setcdr form new-form)
+            form)))
+     ((symbolp form)
+      (if form                          ; Don't position nil!
+          (if (symbol-with-pos-p form)
+              form
+            (position-symbol form call-pos))))
+     ((and (or (vectorp form) (recordp form)))
+      (let ((len (length form))
+            (i 0)
+            )
+        (while (and (< i len)
+                    (not (setq new-form (macroexp--posify-form-1
+                                         (aref form i) call-pos (1- depth)))))
+          (setq i (1+ i)))
+        (when (< i len)
+          (aset form i new-form)
+          form))))))
+
+(defun macroexp--posify-form (form call-pos)
+  "Try to apply the position CALL-POS to the form FORM, if needed.
+CALL-POS is a buffer position, a number.  FORM may be any lisp form,
+and is typically the output form returned by a macro expansion.
+
+Apply CALL-POS to FORM as a symbol with position, such that
+`byte-compile--first-symbol-with-pos' can later return it.  If there is
+already a symbol with position in a \"canonical\" position for that
+function, leave it unchanged and do nothing.  Return the possibly
+modified FORM."
+  (let ((new-form (macroexp--posify-form-1 form call-pos 10)))
+    (or new-form form)))
+
+(defmacro macroexp-preserve-posification (pos-form &rest body)
+  "Evaluate BODY..., posifying the result with POS-FORM's position, if any.
+If the result of body happens to have a position already, we do not
+change this."
+  (declare (debug (sexp body)) (indent 1))
+  `(let ((call-pos (cond
+                    ((consp ,pos-form)
+                     (and (symbol-with-pos-p (car ,pos-form))
+                          (symbol-with-pos-pos (car ,pos-form))))
+                    ((symbol-with-pos-p ,pos-form)
+                     (symbol-with-pos-pos ,pos-form))))
+         (new-value (progn ,@body)))
+     (if (and call-pos
+              (not (or (and (consp new-value)
+                            (symbol-with-pos-p (car new-value)))
+                       (and (symbol-with-pos-p new-value)))))
+         (macroexp--posify-form new-value call-pos)
+       new-value)))
+
 (defun macroexp-macroexpand (form env)
   "Like `macroexpand' but checking obsolescence."
   (let* ((macroexpand-all-environment env)
          new-form)
-    (while (not (eq form (setq new-form (macroexpand-1 form env))))
-      (let ((fun (car-safe form)))
-        (setq form
-              (if (and fun (symbolp fun)
-                       (get fun 'byte-obsolete-info))
-                  (macroexp-warn-and-return
-                   (macroexp--obsolete-warning
-                    fun (get fun 'byte-obsolete-info)
-                    (if (symbolp (symbol-function fun)) "alias" "macro"))
-                   new-form (list 'obsolete fun) nil fun)
-                new-form))))
-    form))
+    (macroexp-preserve-posification
+        form
+      (while (not (eq form (setq new-form (macroexpand-1 form env))))
+        (let ((fun (car-safe form)))
+          (setq form
+                (if (and fun (symbolp fun)
+                         (get fun 'byte-obsolete-info))
+                    (macroexp-warn-and-return
+                     (macroexp--obsolete-warning
+                      fun (get fun 'byte-obsolete-info)
+                      (if (symbolp (symbol-function fun)) "alias" "macro"))
+                     new-form (list 'obsolete fun) nil fun)
+                  new-form))))
+      form)))
 
 (defun macroexp--unfold-lambda (form &optional name)
   (or name (setq name "anonymous lambda"))
@@ -329,6 +410,9 @@ Only valid during macro-expansion."
   "Expand all macros in FORM.
 This is an internal version of `macroexpand-all'.
 Assumes the caller has bound `macroexpand-all-environment'."
+  ;; Note that this function must preserve any position on FORM in the
+  ;; function's return value.  See the page "Symbols with Position" in
+  ;; the elisp manual.
   (macroexp--with-extended-form-stack form
       (if (eq (car-safe form) 'backquote-list*)
           ;; Special-case `backquote-list*', as it is normally a macro that
diff --git a/test/lisp/emacs-lisp/bytecomp-resources/macro-warning-position-2.el b/test/lisp/emacs-lisp/bytecomp-resources/macro-warning-position-2.el
new file mode 100644 (file)
index 0000000..7868e6e
--- /dev/null
@@ -0,0 +1,19 @@
+;; -*- lexical-binding:t -*-
+(eval-and-compile
+  (defmacro increase ()
+    `(let ((foo ,(point-max)))
+       (cond
+       ((consp foo)
+        (message "consp %s" foo)
+        foo)
+       ((numberp foo)
+        (1+ fooo))                     ; Note the misspelling.
+       (t (message "Something else: %s" foo))))))
+
+(defun call-increase (bar)
+  (cond
+   ((not (or (consp bar)
+            (numberp bar)))
+    bar)
+   (t (increase))))
+    
diff --git a/test/lisp/emacs-lisp/bytecomp-resources/macro-warning-position.el b/test/lisp/emacs-lisp/bytecomp-resources/macro-warning-position.el
new file mode 100644 (file)
index 0000000..d35fada
--- /dev/null
@@ -0,0 +1,19 @@
+;; -*- lexical-binding:t -*-
+(eval-and-compile
+  (defmacro increase ()
+    `(let ((foo (point-max)))
+       (cond
+       ((consp foo)
+        (message "consp %s" foo)
+        foo)
+       ((numberp foo)
+        (1+ fooo))                     ; Note the misspelling.
+       (t (message "Something else: %s" foo))))))
+
+(defun call-increase (bar)
+  (cond
+   ((not (or (consp bar)
+            (numberp bar)))
+    bar)
+   (t (increase))))
+    
index 7382928da15acd046dcf57e1d06fb09164022cbb..5d95e9b0ee7188a786941bc39d5c8e7f552debc8 100644 (file)
@@ -1285,6 +1285,11 @@ byte-compiled.  Run with dynamic binding."
  "warn-make-process-missing-keyword-value.el"
  "missing value for keyword argument :command")
 
+;;;; NEW STOUGH, 2025-07-13
+(bytecomp--define-warning-file-test "macro-warning-position.el" ":18:8:")
+
+(bytecomp--define-warning-file-test "macro-warning-position-2.el" ":18:8:")
+;;;; END OF NEW STOUGH
 \f
 ;;;; Macro expansion.