Subject: bug#55156: closed (Re: bug#55156: [PATCH] eval.c: New functions `defvar-f` and `defconst-f`)

From: [email-masked] (GNU bug Tracking System)
Date: Fri, 27 May 2022 01:28:02 +0000

Your bug report

#55156: [PATCH] eval.c: New functions `defvar-f` and `defconst-f`

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
55156: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=55156
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#55156: [PATCH] eval.c: New functions `defvar-f` and `defconst-f`
From: Stefan Monnier <[email-masked]>
To: Eli Zaretskii <[email-masked]>
Cc: [email-masked], [email-masked]
Date: Thu, 26 May 2022 21:27:33 -0400
List-Id: <debbugs-submit.debbugs.gnu.org>

>> (byte-compile-defvar): Simplify using the new `def(car|const)-1` functions.
>                                                     ^^^^^^^^^^^
> Can we please not use such "shorthands" in the log messages?

Fixed, and then pushed, thanks,

        Stefan

Subject: [PATCH] eval.c: New functions `defvar-f` and `defconst-f`
From: Stefan Monnier <[email-masked]>
To: [email-masked]
Date: Wed, 27 Apr 2022 17:46:22 -0400
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

Tags: patch

>From b0e07492cfe82ab3c49e663e72188ba5e90b7f76 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <[email-masked]>
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