master 541f693ca2e: Create macro 'pcase-let*-strict'

Stefan Monnier via Mailing list for Emacs changes <[email protected]> Thu, 16 Jul 2026 16:57:11 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit 541f693ca2e8de166cfe52c39bbfc497142215e9
Author: Earl Hyatt <[email protected]>
Commit: Stefan Monnier <[email protected]>

    Create macro 'pcase-let*-strict'
    
    Create a version of 'pcase-let*' that signals an error when a 'pcase'
    pattern does not match its corresponding value.
    
    * lisp/emacs-lisp/pcase.el (pcase--let*): Add the optional argument
    'strict' to generate code for signaling an error when a 'pcase'
    pattern does not match its corresponding value.
    * lisp/emacs-lisp/pcase.el (pcase-let*-strict): Add the new macro.
    * test/lisp/emacs-lisp/pcase-tests.el (pcase-tests-let*-strict):
    Add new test.
    * etc/NEWS (Pcase): Add heading for 'pcase'.
    * etc/NEWS (Add 'pcase-let*-strict'): Add news entry for new macro.
---
 doc/lispref/control.texi            |  7 ++++++
 etc/NEWS                            |  9 ++++++++
 lisp/emacs-lisp/pcase.el            | 45 +++++++++++++++++++++++++++++--------
 test/lisp/emacs-lisp/pcase-tests.el | 15 +++++++++++++
 4 files changed, 67 insertions(+), 9 deletions(-)

diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi
index d3662f727cc..909c69a854e 100644
--- a/doc/lispref/control.texi
+++ b/doc/lispref/control.texi
@@ -1489,6 +1489,13 @@ introduced in each one of the @var{bindings} are available in the
 being available in @var{body}.
 @end defmac
 
+@defmac pcase-let*-strict bindings body@dots{}
+Like @code{pcase-let*}, but signals an error if a @var{pattern} does
+not match its corresponding @var{exp}.  For example, this can alert
+you when a value that you would like to destructure is not as
+expected.
+@end defmac
+
 @defmac pcase-dolist (pattern list) body@dots{}
 Execute @var{body} once for each element of @var{list}, on each
 iteration performing a destructuring binding of variables in
diff --git a/etc/NEWS b/etc/NEWS
index 4e3f8508da4..85d1af8d71c 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -211,6 +211,15 @@ To install the grammars, use 'M-x markdown-ts-mode-install-parsers'.
 
 * Incompatible Lisp Changes in Emacs 32.1
 
+** Pcase
+
++++
+*** Add 'pcase-let*-strict'
+This macro is like 'pcase-let*', but signals an error if a 'pcase'
+pattern does not match its corresponding value.  This can be useful for
+destructuring values when you do not wish to continue if the
+corresponding value is not as expected.
+
 
 * Lisp Changes in Emacs 32.1
 
diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el
index 166b346fbbe..6f9312a50d3 100644
--- a/lisp/emacs-lisp/pcase.el
+++ b/lisp/emacs-lisp/pcase.el
@@ -268,22 +268,28 @@ nil."
     `(lambda ,args ,@(car parsed-body)
        (pcase-let* ,(nreverse bindings) ,@(cdr parsed-body)))))
 
-(defun pcase--let* (bindings body)
+(defun pcase--let* (bindings body &optional strict)
   (cond
    ((null bindings) (macroexp-progn body))
    ((pcase--trivial-upat-p (caar bindings))
     (macroexp-let* `(,(car bindings)) (pcase--let* (cdr bindings) body)))
    (t
-    (let ((binding (pop bindings)))
+    (let* ((binding (pop bindings))
+           (x (gensym "x"))
+           (pcase--dontwarn-upats (cons x pcase--dontwarn-upats)))
       (pcase--expand
        (cadr binding)
-       `((,(car binding) ,(pcase--let* bindings body))
-         ;; We can either signal an error here, or just use `pcase--dontcare'
-         ;; which generates more efficient code.  In practice, if we use
-         ;; `pcase--dontcare' we will still often get an error and the few
-         ;; cases where we don't do not matter that much, so
-         ;; it's a better choice.
-         (pcase--dontcare nil)))))))
+       `((,(car binding) ,(pcase--let* bindings body strict))
+         ,(if strict
+              `(,x (error "`pcase' pattern does not match value: %S %S"
+                          (quote ,(car binding))
+                          ,x))
+            ;; We can either signal an error here, or just use `pcase--dontcare'
+            ;; which generates more efficient code.  In practice, if we use
+            ;; `pcase--dontcare' we will still often get an error and the few
+            ;; cases where we don't do not matter that much, so
+            ;; it's a better choice.
+            '(pcase--dontcare nil))))))))
 
 ;;;###autoload
 (defmacro pcase-let* (bindings &rest body)
@@ -305,6 +311,27 @@ undetected, binding variables to arbitrary values, such as nil."
         (puthash bindings (cons body expansion) pcase--memoize)
         expansion))))
 
+;;;###autoload
+(defmacro pcase-let*-strict (bindings &rest body)
+  "Like `pcase-let*', but signal an error when a pattern does not match.
+As with `pcase-let*', BINDINGS are of the form (PATTERN EXP), but the
+EXP in each binding in BINDINGS can use the results of the destructuring
+bindings that precede it in BINDINGS' order.
+
+Each EXP should match its respective PATTERN (i.e. be of structure
+compatible to PATTERN); a mismatch may signal an error or may go
+undetected, binding variables to arbitrary values, such as nil."
+  (declare (indent 1)
+           (debug ((&rest (pcase-PAT &optional form)) body)))
+  (let ((cached (gethash bindings pcase--memoize)))
+    ;; cached = (BODY . EXPANSION)
+    (if (equal (car cached) body)
+        (cdr cached)
+      (let ((expansion (pcase--let* bindings body t)))
+        (puthash bindings (cons body expansion) pcase--memoize)
+        expansion))))
+
+
 ;;;###autoload
 (defmacro pcase-let (bindings &rest body)
   "Like `let', but supports destructuring BINDINGS using `pcase' patterns.
diff --git a/test/lisp/emacs-lisp/pcase-tests.el b/test/lisp/emacs-lisp/pcase-tests.el
index 9b8a643c731..866525343eb 100644
--- a/test/lisp/emacs-lisp/pcase-tests.el
+++ b/test/lisp/emacs-lisp/pcase-tests.el
@@ -210,4 +210,19 @@
     (should (equal (f4 nil) 1))
     (should (equal (f4 '(a)) 2))))
 
+(ert-deftest pcase-tests-let*-strict ()
+  (should (equal '(1 2 4 5)
+                 (pcase-let*-strict ((`(,a ,b) (list 1 2))
+                                     (`[,c ,d] (vector 4 5)))
+                   (list a b c d))))
+
+  (should (equal '(1 2 nil nil)
+                 (pcase-let* ((`(,a ,b) (list 1 2))
+                              (`(,c ,d) (vector 4 5)))
+                   (list a b c d))))
+
+  (should-error (pcase-let*-strict ((`(,a ,b) (list 1 2))
+                                    (`(,c ,d) (vector 4 5)))
+                  (list a b c d))))
+
 ;;; pcase-tests.el ends here.