[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
bug#55156: [PATCH] eval.c: New functions `defvar-f` and `defconst-f`
From: |
Stefan Monnier |
Subject: |
bug#55156: [PATCH] eval.c: New functions `defvar-f` and `defconst-f` |
Date: |
Wed, 27 Apr 2022 17:46:22 -0400 |
Tags: patch
Tags: patch
>From b0e07492cfe82ab3c49e663e72188ba5e90b7f76 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier@iro.umontreal.ca>
Date: Wed, 27 Apr 2022 17:44:20 -0400
Subject: [PATCH] eval.c: New functions `defvar-f` and `defconst-f`
The bytecode interpreter can't directly call special forms, so
the byte-compiler usually converts special forms into some sequence of
byte codes (basically, providing a duplicate definition of the special
form). There are still two exceptions to this: `defconst` and `defvar`,
where the compiler instead generates a convoluted chunk of code like:
(funcall '(lambda (x) (defvar <sym> x <doc>)) <value>)
where the quote makes sure we keep the function non-compiled, so as
to end up running the special form at run time.
The patch below gets rid of this workaround by introducing `defvar-f`
and `defconst-f` which provide a *functional* interface to the
functionality of the corresponding special form.
This changes the behavior of (defvar <sym> <exp>) because
(defvar-f '<sym> <exp>) will now always evaluate <exp> whereas
previously the doc promised that <exp> would only be evaluated if
<sym> was not yet bound.
This sounds scary, but the reality is less so: while the behavior of
the special form obeyed its doc in this respect, the behavior of the
convoluted code generated by the byte-compiler did not(!) and always
evaluated the <exp> part anyway. So this patch also aligns the two
semantics to provide the same behavior.
* src/eval.c (Fdefvar_f, Fdefconst_f): New functions, extracted from
`Fdef(var|const)`.
(Fdefvar, Fdefconst): Use them.
(syms_of_eval): `defsubr` the new functions.
* lisp/emacs-lisp/bytecomp.el (byte-compile-tmp-var): Delete const.
(byte-compile-defvar): Simplify using the new `def(car|const)-f` functions.
* doc/lispref/variables.texi (Defining Variables): Adjust the doc of
`defvar` to reflect the actual semantics implemented. Don't state
explicitly if the `value` is always evaluated or not.
---
doc/lispref/variables.texi | 14 ++++----
lisp/emacs-lisp/bytecomp.el | 20 ++++-------
src/eval.c | 72 +++++++++++++++++++++++--------------
3 files changed, 58 insertions(+), 48 deletions(-)
diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi
index f0e3f337a69..264fcbcfe8e 100644
--- a/doc/lispref/variables.texi
+++ b/doc/lispref/variables.texi
@@ -510,10 +510,10 @@ Defining Variables
(@pxref{Variable Scoping}).
If @var{value} is specified, and @var{symbol} is void (i.e., it has no
-dynamically bound value; @pxref{Void Variables}), then @var{value} is
-evaluated and @var{symbol} is set to the result. But if @var{symbol}
-is not void, @var{value} is not evaluated, and @var{symbol}'s value is
-left unchanged. If @var{value} is omitted, the value of @var{symbol}
+dynamically bound value; @pxref{Void Variables}), then @var{symbol} is
+set to the result of evaluating of @var{value}. But if @var{symbol}
+is not void @var{symbol}'s value is left unchanged.
+If @var{value} is omitted, the value of @var{symbol}
is not changed in any case.
Note that specifying a value, even @code{nil}, marks the variable as
@@ -527,9 +527,9 @@ Defining Variables
rather than the buffer-local binding. It sets the default value if
the default value is void. @xref{Buffer-Local Variables}.
-If @var{symbol} is already lexically bound (e.g., if the @code{defvar}
-form occurs in a @code{let} form with lexical binding enabled), then
-@code{defvar} sets the dynamic value. The lexical binding remains in
+If @var{symbol} is already let bound (e.g., if the @code{defvar}
+form occurs in a @code{let} form), then @code{defvar} sets the dynamic
+outer value. The let binding remains in
effect until its binding construct exits. @xref{Variable Scoping}.
@cindex @code{eval-defun}, and @code{defvar} forms
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index c0dffe544cf..68a664c7129 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -4887,8 +4887,6 @@ byte-compile-make-obsolete-variable
(push (nth 1 (nth 1 form)) byte-compile-global-not-obsolete-vars))
(byte-compile-normal-call form))
-(defconst byte-compile-tmp-var (make-symbol "def-tmp-var"))
-
(defun byte-compile-defvar (form)
;; This is not used for file-level defvar/consts.
(when (and (symbolp (nth 1 form))
@@ -4901,7 +4899,6 @@ byte-compile-defvar
(byte-compile-docstring-length-warn form)
(let ((fun (nth 0 form))
(var (nth 1 form))
- (value (nth 2 form))
(string (nth 3 form)))
(when (or (> (length form) 4)
(and (eq fun 'defconst) (null (cddr form))))
@@ -4922,17 +4919,12 @@ byte-compile-defvar
"third arg to `%s %s' is not a string: %s"
fun var string))
(byte-compile-form-do-effect
- (if (cddr form) ; `value' provided
- ;; Quote with `quote' to prevent byte-compiling the body,
- ;; which would lead to an inf-loop.
- `(funcall '(lambda (,byte-compile-tmp-var)
- (,fun ,var ,byte-compile-tmp-var ,@(nthcdr 3 form)))
- ,value)
- (if (eq fun 'defconst)
- ;; This will signal an appropriate error at runtime.
- `(eval ',form)
- ;; A simple (defvar foo) just returns foo.
- `',var)))))
+ (if (or (cddr form) ; `value' provided
+ (eq fun 'defconst))
+ ;; Delegate the actual work to the `-f' version of the special form.
+ `(,(intern (format "%s-f" fun)) ',var ,@(nthcdr 2 form))
+ ;; A simple (defvar foo) just returns foo.
+ `',var))))
(defun byte-compile-autoload (form)
(and (macroexp-const-p (nth 1 form))
diff --git a/src/eval.c b/src/eval.c
index 77ec47e2b79..10212708c23 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -763,17 +763,14 @@ DEFUN ("defvar", Fdefvar, Sdefvar, 1, UNEVALLED, 0,
so that it is always dynamically bound even if `lexical-binding' is t.
If SYMBOL's value is void and the optional argument INITVALUE is
-provided, INITVALUE is evaluated and the result used to set SYMBOL's
-value. If SYMBOL is buffer-local, its default value is what is set;
+provided, INITVALUE is used to set SYMBOL's value.
+If SYMBOL is buffer-local, its default value is what is set;
buffer-local values are not affected. If INITVALUE is missing,
SYMBOL's value is not set.
-If SYMBOL has a local binding, then this form affects the local
-binding. This is usually not what you want. Thus, if you need to
-load a file defining variables, with this form or with `defconst' or
-`defcustom', you should always load that file _outside_ any bindings
-for these variables. (`defconst' and `defcustom' behave similarly in
-this respect.)
+If SYMBOL is let-bound, then this form does not affect the local let
+binding but the outer (toplevel) binding.
+(`defcustom' behaves similarly in this respect.)
The optional argument DOCSTRING is a documentation string for the
variable.
@@ -784,7 +781,7 @@ DEFUN ("defvar", Fdefvar, Sdefvar, 1, UNEVALLED, 0,
usage: (defvar SYMBOL &optional INITVALUE DOCSTRING) */)
(Lisp_Object args)
{
- Lisp_Object sym, tem, tail;
+ Lisp_Object sym, tail;
sym = XCAR (args);
tail = XCDR (args);
@@ -796,24 +793,8 @@ DEFUN ("defvar", Fdefvar, Sdefvar, 1, UNEVALLED, 0,
if (!NILP (XCDR (tail)) && !NILP (XCDR (XCDR (tail))))
error ("Too many arguments");
Lisp_Object exp = XCAR (tail);
-
- tem = Fdefault_boundp (sym);
tail = XCDR (tail);
-
- /* Do it before evaluating the initial value, for self-references. */
- Finternal__define_uninitialized_variable (sym, CAR (tail));
-
- if (NILP (tem))
- Fset_default (sym, eval_sub (exp));
- else
- { /* Check if there is really a global binding rather than just a let
- binding that shadows the global unboundness of the var. */
- union specbinding *binding = default_toplevel_binding (sym);
- if (binding && EQ (specpdl_old_value (binding), Qunbound))
- {
- set_specpdl_old_value (binding, eval_sub (exp));
- }
- }
+ return Fdefvar_f (sym, eval_sub (exp), CAR (tail));
}
else if (!NILP (Vinternal_interpreter_environment)
&& (SYMBOLP (sym) && !XSYMBOL (sym)->u.s.declared_special))
@@ -832,6 +813,33 @@ DEFUN ("defvar", Fdefvar, Sdefvar, 1, UNEVALLED, 0,
return sym;
}
+DEFUN ("defvar-f", Fdefvar_f, Sdefvar_f, 2, 3, 0,
+ doc: /* Like `defvar' but as a function. */)
+ (Lisp_Object sym, Lisp_Object initvalue, Lisp_Object docstring)
+{
+ Lisp_Object tem;
+
+ CHECK_SYMBOL (sym);
+
+ tem = Fdefault_boundp (sym);
+
+ /* Do it before evaluating the initial value, for self-references. */
+ Finternal__define_uninitialized_variable (sym, docstring);
+
+ if (NILP (tem))
+ Fset_default (sym, initvalue);
+ else
+ { /* Check if there is really a global binding rather than just a let
+ binding that shadows the global unboundness of the var. */
+ union specbinding *binding = default_toplevel_binding (sym);
+ if (binding && EQ (specpdl_old_value (binding), Qunbound))
+ {
+ set_specpdl_old_value (binding, initvalue);
+ }
+ }
+ return sym;
+}
+
DEFUN ("defconst", Fdefconst, Sdefconst, 2, UNEVALLED, 0,
doc: /* Define SYMBOL as a constant variable.
This declares that neither programs nor users should ever change the
@@ -861,9 +869,17 @@ DEFUN ("defconst", Fdefconst, Sdefconst, 2, UNEVALLED, 0,
error ("Too many arguments");
docstring = XCAR (XCDR (XCDR (args)));
}
+ tem = eval_sub (XCAR (XCDR (args)));
+ return Fdefconst_f (sym, tem, docstring);
+}
+DEFUN ("defconst-f", Fdefconst_f, Sdefconst_f, 2, 3, 0,
+ doc: /* Like `defconst' but as a function. */)
+ (Lisp_Object sym, Lisp_Object initvalue, Lisp_Object docstring)
+{
+ CHECK_SYMBOL (sym);
+ Lisp_Object tem = initvalue;
Finternal__define_uninitialized_variable (sym, docstring);
- tem = eval_sub (XCAR (XCDR (args)));
if (!NILP (Vpurify_flag))
tem = Fpurecopy (tem);
Fset_default (sym, tem); /* FIXME: set-default-toplevel-value? */
@@ -4325,9 +4341,11 @@ syms_of_eval (void)
defsubr (&Sdefault_toplevel_value);
defsubr (&Sset_default_toplevel_value);
defsubr (&Sdefvar);
+ defsubr (&Sdefvar_f);
defsubr (&Sdefvaralias);
DEFSYM (Qdefvaralias, "defvaralias");
defsubr (&Sdefconst);
+ defsubr (&Sdefconst_f);
defsubr (&Sinternal__define_uninitialized_variable);
defsubr (&Smake_var_non_special);
defsubr (&Slet);
--
2.35.1
- bug#55156: [PATCH] eval.c: New functions `defvar-f` and `defconst-f`,
Stefan Monnier <=
bug#55156: [PATCH] eval.c: New functions `defvar-f` and `defconst-f`, Eli Zaretskii, 2022/04/28