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)