]> git.eshelyaron.com Git - emacs.git/commitdiff
Body of dynamic let-bindings is not in tail position
authorMattias Engdegård <mattiase@acm.org>
Mon, 20 Dec 2021 10:59:22 +0000 (11:59 +0100)
committerMattias Engdegård <mattiase@acm.org>
Mon, 20 Dec 2021 15:26:02 +0000 (16:26 +0100)
This fixes a known bug in `named-let`.

* lisp/emacs-lisp/cl-macs.el (cl--self-tco): Prevent TCO from inside
dynamic variable bindings.
* test/lisp/emacs-lisp/cl-macs-tests.el (cl-macs--labels): Add test.

lisp/emacs-lisp/cl-macs.el
test/lisp/emacs-lisp/cl-macs-tests.el

index f78fdcf0085436f8114837b3df2c750e1132a4a4..9e93e8775d510be73ee4f40644e33ddd7b9c0218 100644 (file)
@@ -2139,9 +2139,14 @@ Like `cl-flet' but the definitions can refer to previous ones.
                    ;; setq the fresh new `ofargs' vars instead ;-)
                    (let ((shadowings
                           (mapcar (lambda (b) (if (consp b) (car b) b)) bindings)))
-                     ;; If `var' is shadowed, then it clearly can't be
-                     ;; tail-called any more.
-                     (not (memq var shadowings)))))
+                     (and
+                      ;; If `var' is shadowed, then it clearly can't be
+                      ;; tail-called any more.
+                      (not (memq var shadowings))
+                      ;; If any of the new bindings is a dynamic
+                      ;; variable, the body is not in tail position.
+                      (not (cl-some #'macroexp--dynamic-variable-p
+                                    shadowings))))))
              `(,(car exp) ,bindings . ,(funcall opt-exps exps)))
             ((and `(condition-case ,err-var ,bodyform . ,handlers)
                   (guard (not (eq err-var var))))
index 13da60ec45eccb8d74e60e3b29feac98e81ba9d3..ced2cc10f30114443648e66069ecfa8f355646d8 100644 (file)
@@ -666,7 +666,24 @@ collection clause."
   (should (pcase (macroexpand
                   '(cl-labels ((len (xs n) (if xs (len (cdr xs) (1+ n)) n)))
                      #'len))
-            (`(function (lambda (,_ ,_) . ,_)) t))))
+            (`(function (lambda (,_ ,_) . ,_)) t)))
+
+  ;; Verify that there is no tail position inside dynamic variable bindings.
+  (defvar dyn-var)
+  (let ((dyn-var 'a))
+    (cl-labels ((f (x) (if x
+                           dyn-var
+                         (let ((dyn-var 'b))
+                           (f dyn-var)))))
+      (should (equal (f nil) 'b))))
+
+  ;; Control: same as above but with lexical binding.
+  (let ((lex-var 'a))
+    (cl-labels ((f (x) (if x
+                           lex-var
+                         (let ((lex-var 'b))
+                           (f lex-var)))))
+      (should (equal (f nil) 'a)))))
 
 (ert-deftest cl-macs--progv ()
   (defvar cl-macs--test)