summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/cl-macs.el
diff options
context:
space:
mode:
authorStefan Monnier <monnier@iro.umontreal.ca>2024-11-12 22:58:53 -0500
committerStefan Monnier <monnier@iro.umontreal.ca>2024-11-12 22:58:53 -0500
commit79400f4f18b80cdde72eda86023e41a81d09a164 (patch)
tree105b767dabd0e0fe14dda6881ad50356a4518822 /lisp/emacs-lisp/cl-macs.el
parenta7400cb8810373b6d39347a5e0e1ac7152d3abd1 (diff)
downloademacs-79400f4f18b80cdde72eda86023e41a81d09a164.tar.gz
emacs-79400f4f18b80cdde72eda86023e41a81d09a164.tar.bz2
emacs-79400f4f18b80cdde72eda86023e41a81d09a164.zip
(cl-labels): Add support for (FUNC EXP) bindings (bug#59786)
Allow `cl-labels` to use the same (FUNC EXP) bindings as were already added to `cl-flet` in Emacs-25. The Info doc (mistakenly) already documented this new feature. * lisp/emacs-lisp/cl-macs.el (cl--self-tco-on-form): New function. (cl-labels): Use it to add support for (FUNC EXP) bindings. * test/lisp/emacs-lisp/cl-macs-tests.el (cl-macs--labels): Add test for tail-recursive (FUNC EXP) bindings.
Diffstat (limited to 'lisp/emacs-lisp/cl-macs.el')
-rw-r--r--lisp/emacs-lisp/cl-macs.el50
1 files changed, 34 insertions, 16 deletions
diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el
index b37f744b175..65bc2cb9173 100644
--- a/lisp/emacs-lisp/cl-macs.el
+++ b/lisp/emacs-lisp/cl-macs.el
@@ -2247,15 +2247,35 @@ Like `cl-flet' but the definitions can refer to previous ones.
. ,optimized-body))
,retvar)))))))
+(defun cl--self-tco-on-form (var form)
+ ;; Apply self-tco to the function returned by FORM, assuming that
+ ;; it will be bound to VAR.
+ (pcase form
+ (`(function (lambda ,fargs . ,ebody)) form
+ (pcase-let* ((`(,decls . ,body) (macroexp-parse-body ebody))
+ (`(,ofargs . ,obody) (cl--self-tco var fargs body)))
+ `(function (lambda ,ofargs ,@decls . ,obody))))
+ (`(let ,bindings ,form)
+ `(let ,bindings ,(cl--self-tco-on-form var form)))
+ (`(if ,cond ,exp1 ,exp2)
+ `(if ,cond ,(cl--self-tco-on-form var exp1)
+ ,(cl--self-tco-on-form var exp2)))
+ (`(oclosure--fix-type ,exp1 ,exp2)
+ `(oclosure--fix-type ,exp1 ,(cl--self-tco-on-form var exp2)))
+ (_ form)))
+
;;;###autoload
(defmacro cl-labels (bindings &rest body)
"Make local (recursive) function definitions.
-BINDINGS is a list of definitions of the form (FUNC ARGLIST BODY...) where
+BINDINGS is a list of definitions of the form either (FUNC EXP)
+where EXP is a form that should return the function to bind to the
+function name FUNC, or (FUNC ARGLIST BODY...) where
FUNC is the function name, ARGLIST its arguments, and BODY the
-forms of the function body. FUNC is defined in any BODY, as well
+forms of the function body. FUNC is in scope in any BODY or EXP, as well
as FORM, so you can write recursive and mutually recursive
-function definitions. See info node `(cl) Function Bindings' for
-details.
+function definitions, with the caveat that EXPs are evaluated in sequence
+and you cannot call a FUNC before its EXP has been evaluated.
+See info node `(cl) Function Bindings' for details.
\(fn ((FUNC ARGLIST BODY...) ...) FORM...)"
(declare (indent 1) (debug cl-flet))
@@ -2273,18 +2293,16 @@ details.
(unless (assq 'function newenv)
(push (cons 'function #'cl--labels-convert) newenv))
;; Perform self-tail call elimination.
- (setq binds (mapcar
- (lambda (bind)
- (pcase-let*
- ((`(,var ,sargs . ,sbody) bind)
- (`(function (lambda ,fargs . ,ebody))
- (macroexpand-all `(cl-function (lambda ,sargs . ,sbody))
- newenv))
- (`(,ofargs . ,obody)
- (cl--self-tco var fargs ebody)))
- `(,var (function (lambda ,ofargs . ,obody)))))
- (nreverse binds)))
- `(letrec ,binds
+ `(letrec ,(mapcar
+ (lambda (bind)
+ (pcase-let* ((`(,var ,sargs . ,sbody) bind))
+ `(,var ,(cl--self-tco-on-form
+ var (macroexpand-all
+ (if (null sbody)
+ sargs ;A (FUNC EXP) definition.
+ `(cl-function (lambda ,sargs . ,sbody)))
+ newenv)))))
+ (nreverse binds))
. ,(macroexp-unprogn
(macroexpand-all
(macroexp-progn body)