(eval-and-compile): Preserve the surrounding lexical context

Implement a better fix for bug#79634.

* lisp/emacs-lisp/byte-run.el (eval-and-compile):
* lisp/emacs-lisp/bytecomp.el (byte-compile-initial-macro-environment)
<eval-and-compile>: Preserve the surrounding lexical context (the part
available during macroexpansion, i.e. which vars are dynbound).

* lisp/emacs-lisp/rx.el (<pcase> rx): Remove workaround.

* test/lisp/emacs-lisp/macroexp-tests.el
(macroexp--dynbound-eval-and-compile): New test.
This commit is contained in:
Stefan Monnier 2025-10-18 17:45:07 -04:00
parent 48357dc612
commit 4ec24ce2a1
4 changed files with 49 additions and 6 deletions

View file

@ -683,7 +683,8 @@ enabled."
;; When the byte-compiler expands code, this macro is not used, so we're
;; either about to run `body' (plain interpretation) or we're doing eager
;; macroexpansion.
(list 'quote (eval (cons 'progn body) lexical-binding)))
(list 'quote (eval (cons 'progn body)
(when lexical-binding (or macroexp--dynvars t)))))
(defun with-no-warnings (&rest body)
"Like `progn', but prevents compiler warnings in the body."

View file

@ -589,7 +589,11 @@ Only conses are traversed and duplicated, not arrays or any other structure."
macroexpand-all-environment)))
(eval (byte-run-strip-symbol-positions
(bytecomp--copy-tree expanded))
lexical-binding)
(when lexical-binding
(or (append
macroexp--dynvars
byte-compile-bound-variables)
t)))
expanded)))))
(with-suppressed-warnings
. ,(lambda (warnings &rest body)

View file

@ -1685,10 +1685,6 @@ following constructs:
REF can be a number, as usual, or a name
introduced by a previous (let REF ...)
construct."
;; FIXME: We can't rely on the surrounding lexical context because
;; `pcase-defmacro' wraps this function inside an `eval-and-compile',
;; so we have to repeat the (defvar rx--pcase-vars).
(defvar rx--pcase-vars)
(let* ((rx--pcase-vars nil)
(regexp (rx--to-expr (rx--pcase-transform (cons 'seq regexps)))))
`(and (pred stringp)

View file

@ -124,6 +124,48 @@
(dyn dyn dyn dyn)
(dyn dyn dyn lex))))))
(ert-deftest macroexp--dynbound-eval-and-compile ()
(let ((code1 '(progn
(eval-and-compile
(defun my-foo () (bound-and-true-p my-foo))
(defun my-identity (x)
(defvar my-foo)
(let ((my-foo x))
(my-foo))))
(defmacro my-toto (y)
`(list ',y ',(my-identity y)))
(eval-when-compile (my-toto 7))))
(code2 '(progn
(defvar my-foo)
(eval-and-compile
(defun my-foo () (bound-and-true-p my-foo))
(defun my-identity (x)
(let ((my-foo x))
(my-foo))))
(defmacro my-toto (y)
`(list ',y ',(my-identity y)))
(eval-when-compile (my-toto 7))))
(code3 '(progn
(eval-and-compile
(defvar my-foo)
(defun my-foo () (bound-and-true-p my-foo))
(defun my-identity (x)
(let ((my-foo x))
(my-foo))))
(defmacro my-toto (y)
`(list ',y ',(my-identity y)))
(eval-when-compile (my-toto 7)))))
(should (equal (eval code1 t) '(7 7)))
(should (equal (eval code2 t) '(7 7)))
(should (equal (eval code3 t) '(7 7)))
(should (equal (eval (let ((lexical-binding t)) (byte-compile code1)) t)
'(7 7)))
(should (equal (eval (let ((lexical-binding t)) (byte-compile code2)) t)
'(7 7)))
(should (equal (eval (let ((lexical-binding t)) (byte-compile code3)) t)
'(7 7)))
))
(defmacro macroexp--test-macro1 ()
(declare (obsolete "new-replacement" nil))
1)