(cl-block, cl-return-from): Fix bug#75498

* lisp/emacs-lisp/cl-macs.el (cl-block, cl-return-from):
Change encoding so it obeys variable coping (i.e. lexical scoping when
`lexical-binding` is non-nil).
(cl--block-wrapper, cl--block-throw): Adjust accordingly.

* test/lisp/emacs-lisp/cl-macs-tests.el
(cl-macs--test-cl-block-lexbind-bug-75498): New test.
This commit is contained in:
Stefan Monnier 2025-01-16 17:48:21 -05:00
parent 8fc5001ba5
commit dace7fa2ab
3 changed files with 36 additions and 16 deletions

View file

@ -383,6 +383,9 @@ Emacs 25.1), and gnudoit (obsolete since Emacs 25.1).
*** 'cl-labels' now also accepts '(FUNC EXP)' bindings, like 'cl-flet'.
Such bindings make it possible to compute which function to bind to FUNC.
---
*** 'cl-block' names are now lexically scoped, as documented.
** Whitespace
---

View file

@ -901,9 +901,13 @@ references may appear inside macro expansions, but not inside functions
called from BODY."
(declare (indent 1) (debug (symbolp body)))
(if (cl--safe-expr-p `(progn ,@body)) `(progn ,@body)
`(cl--block-wrapper
(catch ',(intern (format "--cl-block-%s--" name))
,@body))))
(let ((var (intern (format "--cl-block-%s--" name))))
`(cl--block-wrapper
;; Build a unique "tag" in the form of a fresh cons.
;; We include `var' in the cons, just in case it help debugging.
(let ((,var (cons ',var nil)))
(catch ,var
,@body))))))
;;;###autoload
(defmacro cl-return (&optional result)
@ -921,7 +925,7 @@ This is compatible with Common Lisp, but note that `defun' and
`defmacro' do not create implicit blocks as they do in Common Lisp."
(declare (indent 1) (debug (symbolp &optional form)))
(let ((name2 (intern (format "--cl-block-%s--" name))))
`(cl--block-throw ',name2 ,result)))
`(cl--block-throw ,name2 ,result)))
;;; The "cl-loop" macro.
@ -3672,20 +3676,24 @@ macro that returns its `&whole' argument."
(defvar cl--active-block-names nil)
(cl-define-compiler-macro cl--block-wrapper (cl-form)
(let* ((cl-entry (cons (nth 1 (nth 1 cl-form)) nil))
(cl--active-block-names (cons cl-entry cl--active-block-names))
(cl-body (macroexpand-all ;Performs compiler-macro expansions.
(macroexp-progn (cddr cl-form))
macroexpand-all-environment)))
;; FIXME: To avoid re-applying macroexpand-all, we'd like to be able
;; to indicate that this return value is already fully expanded.
(if (cdr cl-entry)
`(catch ,(nth 1 cl-form) ,@(macroexp-unprogn cl-body))
cl-body)))
(cl-define-compiler-macro cl--block-wrapper (form)
(pcase form
(`(let ((,var . ,val)) (catch ,var . ,body))
(let* ((cl-entry (cons var nil))
(cl--active-block-names (cons cl-entry cl--active-block-names))
(cl-body (macroexpand-all ;Performs compiler-macro expansions.
(macroexp-progn body)
macroexpand-all-environment)))
;; FIXME: To avoid re-applying macroexpand-all, we'd like to be able
;; to indicate that this return value is already fully expanded.
(if (cdr cl-entry)
`(let ((,var . ,val)) (catch ,var ,@(macroexp-unprogn cl-body)))
cl-body)))
;; `form' was somehow mangled, god knows what happened, let's not touch it.
(_ form)))
(cl-define-compiler-macro cl--block-throw (cl-tag cl-value)
(let ((cl-found (assq (nth 1 cl-tag) cl--active-block-names)))
(let ((cl-found (and (symbolp cl-tag) (assq cl-tag cl--active-block-names))))
(if cl-found (setcdr cl-found t)))
`(throw ,cl-tag ,cl-value))

View file

@ -728,6 +728,15 @@ collection clause."
(cons (f1 7) 8)))
'(7 . 8))))
(ert-deftest cl-macs--test-cl-block-lexbind-bug-75498 ()
(should (equal
(let ((ret (lambda (f)
(cl-block a (funcall f) (cl-return-from a :ret)))))
(cl-block a
(list :oops
(funcall ret (lambda () (cl-return-from a :clo))))))
:clo)))
(ert-deftest cl-flet/edebug ()
"Check that we can instrument `cl-flet' forms (bug#65344)."
(with-temp-buffer