summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp
diff options
context:
space:
mode:
authorMattias EngdegÄrd <mattiase@acm.org>2021-12-11 21:39:19 +0100
committerMattias EngdegÄrd <mattiase@acm.org>2021-12-20 16:26:02 +0100
commit8706f6fde13729bf330693cfd163773583e526a9 (patch)
tree16b7674aa4c7604d6b5b5f96a40e840ecc2fd1f0 /lisp/emacs-lisp
parent3259f399d46dabff5e0a87169d3d3455729d8681 (diff)
downloademacs-8706f6fde13729bf330693cfd163773583e526a9.tar.gz
emacs-8706f6fde13729bf330693cfd163773583e526a9.tar.bz2
emacs-8706f6fde13729bf330693cfd163773583e526a9.zip
Add `macroexp--dynamic-variable-p`
This predicate can be used for discriminating between lexically and dynamically bound variables during macro-expansion (only). It is restricted to internal use for the time being. * lisp/emacs-lisp/bytecomp.el (byte-compile-initial-macro-environment): Use macroexpand--all-toplevel. * lisp/emacs-lisp/macroexp.el (macroexp-dynamic-variable-p): New. (macroexp--expand-all): Maintain macroexp--dynvars. (macroexpand-all): Rebind macroexp--dynvars. (macroexpand--all-toplevel): New. (internal-macroexpand-for-load): Use macroexpand--all-toplevel. * src/eval.c (eval_sub): Transfer defvar declarations from Vinternal_interpreter_environment into macroexp--dynvars during lazy macro-expansion. * src/lread.c (readevalloop): Rebind macroexp--dynvars around read-and-evaluate operations. (syms_of_lread): Define macroexp--dynvars. * test/lisp/emacs-lisp/macroexp-resources/vk.el: New file. * test/lisp/emacs-lisp/macroexp-tests.el (macroexp-tests--run-emacs) (macroexp-tests--eval-in-subprocess) (macroexp-tests--byte-compile-in-subprocess) (macroexp--tests-dynamic-variable-p): Add tests.
Diffstat (limited to 'lisp/emacs-lisp')
-rw-r--r--lisp/emacs-lisp/bytecomp.el2
-rw-r--r--lisp/emacs-lisp/macroexp.el60
2 files changed, 42 insertions, 20 deletions
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index a98c9197a06..11107ec0f6d 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -510,7 +510,7 @@ Return the compile-time value of FORM."
;; whether to compile as byte-compile-form
;; or byte-compile-file-form.
(let ((expanded
- (macroexpand-all
+ (macroexpand--all-toplevel
form
macroexpand-all-environment)))
(eval expanded lexical-binding)
diff --git a/lisp/emacs-lisp/macroexp.el b/lisp/emacs-lisp/macroexp.el
index a20c424e2bd..c04cbb7fffd 100644
--- a/lisp/emacs-lisp/macroexp.el
+++ b/lisp/emacs-lisp/macroexp.el
@@ -289,6 +289,16 @@ is executed without being compiled first."
`(let ,(nreverse bindings) . ,body)
(macroexp-progn body)))))
+(defun macroexp--dynamic-variable-p (var)
+ "Whether the variable VAR is dynamically scoped.
+Only valid during macro-expansion."
+ (defvar byte-compile-bound-variables)
+ (or (not lexical-binding)
+ (special-variable-p var)
+ (memq var macroexp--dynvars)
+ (and (boundp 'byte-compile-bound-variables)
+ (memq var byte-compile-bound-variables))))
+
(defun macroexp--expand-all (form)
"Expand all macros in FORM.
This is an internal version of `macroexpand-all'.
@@ -316,28 +326,32 @@ Assumes the caller has bound `macroexpand-all-environment'."
(cddr form))
(cdr form))
form))
- (`(,(or 'defvar 'defconst) . ,_) (macroexp--all-forms form 2))
+ (`(,(or 'defvar 'defconst) ,(and name (pred symbolp)) . ,_)
+ (push name macroexp--dynvars)
+ (macroexp--all-forms form 2))
(`(function ,(and f `(lambda . ,_)))
- (macroexp--cons 'function
- (macroexp--cons (macroexp--all-forms f 2)
- nil
- (cdr form))
- form))
+ (let ((macroexp--dynvars macroexp--dynvars))
+ (macroexp--cons 'function
+ (macroexp--cons (macroexp--all-forms f 2)
+ nil
+ (cdr form))
+ form)))
(`(,(or 'function 'quote) . ,_) form)
(`(,(and fun (or 'let 'let*)) . ,(or `(,bindings . ,body)
pcase--dontcare))
- (macroexp--cons
- fun
- (macroexp--cons
- (macroexp--all-clauses bindings 1)
- (if (null body)
- (macroexp-unprogn
- (macroexp-warn-and-return
- (format "Empty %s body" fun)
- nil nil 'compile-only))
- (macroexp--all-forms body))
- (cdr form))
- form))
+ (let ((macroexp--dynvars macroexp--dynvars))
+ (macroexp--cons
+ fun
+ (macroexp--cons
+ (macroexp--all-clauses bindings 1)
+ (if (null body)
+ (macroexp-unprogn
+ (macroexp-warn-and-return
+ (format "Empty %s body" fun)
+ nil nil 'compile-only))
+ (macroexp--all-forms body))
+ (cdr form))
+ form)))
(`(,(and fun `(lambda . ,_)) . ,args)
;; Embedded lambda in function position.
;; If the byte-optimizer is loaded, try to unfold this,
@@ -421,6 +435,14 @@ Assumes the caller has bound `macroexpand-all-environment'."
If no macros are expanded, FORM is returned unchanged.
The second optional arg ENVIRONMENT specifies an environment of macro
definitions to shadow the loaded ones for use in file byte-compilation."
+ (let ((macroexpand-all-environment environment)
+ (macroexp--dynvars macroexp--dynvars))
+ (macroexp--expand-all form)))
+
+;; This function is like `macroexpand-all' but for use with top-level
+;; forms. It does not dynbind `macroexp--dynvars' because we want
+;; top-level `defvar' declarations to be recorded in that variable.
+(defun macroexpand--all-toplevel (form &optional environment)
(let ((macroexpand-all-environment environment))
(macroexp--expand-all form)))
@@ -706,7 +728,7 @@ test of free variables in the following ways:
(let ((macroexp--pending-eager-loads
(cons load-file-name macroexp--pending-eager-loads)))
(if full-p
- (macroexpand-all form)
+ (macroexpand--all-toplevel form)
(macroexpand form)))
(error
;; Hopefully this shouldn't happen thanks to the cycle detection,