From b0e07492cfe82ab3c49e663e72188ba5e90b7f76 Mon Sep 17 00:00:00 2001 From: Stefan Monnier 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 x )) ) 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 ) because (defvar-f ' ) will now always evaluate whereas previously the doc promised that would only be evaluated if 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 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