branch: elpa/cider
commit 2155a400f975e7334e120c3b304985453065acf7
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>

    [#4111] Don't gate macroexpand-all on the top-level operator
    
    A fully-recursive expansion can reach macros in nested sub-forms, so
    requiring the form's head to be a macro rejected forms it would have
    usefully expanded (e.g. (inc (when x 1))). cider-macrostep-expand-all
    already worked this way, for the same reason.
---
 CHANGELOG.md                       |  4 ++++
 lisp/cider-macroexpansion.el       | 13 ++++++++-----
 test/cider-macroexpansion-tests.el | 31 +++++++++++++++++++++++++++++++
 3 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2cd006925..895ac6b399 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@
 
 ## master (unreleased)
 
+### Bugs fixed
+
+- [#4111](https://github.com/clojure-emacs/cider/issues/4111): Fix the 
macroexpansion commands refusing to expand `let`, `fn`, `loop` and `letfn` 
(macros that double as special forms), restore the no-op expansion of non-macro 
forms (useful for normalizing reader syntax like `::auto/kw` and `#(...)`), and 
stop gating `cider-macroexpand-all` on the top-level operator (a 
fully-recursive expansion can reach macros in nested sub-forms).
+
 ## 2.0.0 (2026-07-15)
 
 ### New features
diff --git a/lisp/cider-macroexpansion.el b/lisp/cider-macroexpansion.el
index db0df93651..db229fc8ab 100644
--- a/lisp/cider-macroexpansion.el
+++ b/lisp/cider-macroexpansion.el
@@ -136,7 +136,10 @@ required, so it is not meant to be called interactively."
   (pcase-let ((`(,beg . ,end) (or (cider-macroexpansion--form-bounds)
                                   (user-error "No sexp before point to 
expand"))))
     (let ((code (buffer-substring-no-properties beg end)))
-      (cider-ensure-macro (cider-macroexpansion--operator code))
+      ;; A fully-recursive expansion can reach macros in nested sub-forms, so
+      ;; it is useful even when the form's head is not a macro itself.
+      (unless (equal expander "macroexpand-all")
+        (cider-ensure-macro (cider-macroexpansion--operator code)))
       (cider-redraw-macroexpansion-buffer
        (cider-sync-request:macroexpand expander code)
        (current-buffer) beg end))))
@@ -173,11 +176,11 @@ If invoked with a PREFIX argument, use 
\\=`macroexpand\\=` instead of
 
 ;;;###autoload
 (defun cider-macroexpand-all ()
-  "Invoke \\=`macroexpand-all\\=` on the expression preceding point."
+  "Invoke \\=`macroexpand-all\\=` on the expression preceding point.
+The form's head needn't be a macro itself, since a fully-recursive
+expansion can reach macros in nested sub-forms."
   (interactive)
-  (let ((form (cider-last-sexp)))
-    (cider-ensure-macro (cider-macroexpansion--operator form))
-    (cider-macroexpand-expr "macroexpand-all" form)))
+  (cider-macroexpand-expr "macroexpand-all" (cider-last-sexp)))
 
 (defun cider-macroexpand-all-inplace ()
   "Perform inplace \\=`macroexpand-all\\=` on the form before point."
diff --git a/test/cider-macroexpansion-tests.el 
b/test/cider-macroexpansion-tests.el
index 1a8a964c2a..e065c14cab 100644
--- a/test/cider-macroexpansion-tests.el
+++ b/test/cider-macroexpansion-tests.el
@@ -143,6 +143,37 @@
     (expect (cider-macroexpansion--operator "(defn- f [])") :to-equal "defn-")
     (expect (cider-macroexpansion--operator "(-> x f)") :to-equal "->")))
 
+(describe "cider-macroexpand-all"
+  (it "doesn't require the form's head to be a macro"
+    ;; #4111: a fully-recursive expansion can reach macros in nested
+    ;; sub-forms, so the operator must not be gated.
+    (spy-on 'cider-last-sexp :and-return-value "(inc (when x 1))")
+    (spy-on 'cider-ensure-macro)
+    (spy-on 'cider-macroexpand-expr)
+    (cider-macroexpand-all)
+    (expect 'cider-ensure-macro :not :to-have-been-called)
+    (expect 'cider-macroexpand-expr
+            :to-have-been-called-with "macroexpand-all" "(inc (when x 1))")))
+
+(describe "cider-macroexpand-expr-inplace"
+  (it "guards single-step expansion on the operator"
+    (with-temp-buffer
+      (clojure-mode)
+      (insert "(inc 1)")
+      (spy-on 'cider-var-info :and-return-value (nrepl-dict "arglists" 
"([x])"))
+      (expect (cider-macroexpand-expr-inplace "macroexpand-1")
+              :to-throw 'user-error)))
+  (it "skips the operator guard for macroexpand-all"
+    (with-temp-buffer
+      (clojure-mode)
+      (insert "(inc (when x 1))")
+      (spy-on 'cider-ensure-macro)
+      (spy-on 'cider-sync-request:macroexpand
+              :and-return-value "(inc (if x (do 1)))")
+      (cider-macroexpand-expr-inplace "macroexpand-all")
+      (expect 'cider-ensure-macro :not :to-have-been-called)
+      (expect (string-trim (buffer-string)) :to-equal "(inc (if x (do 1)))"))))
+
 (describe "cider-macroexpand-menu--apply-args"
   (it "binds the namespace display style from --ns="
     (let (captured)

Reply via email to