]> git.eshelyaron.com Git - emacs.git/commitdiff
(defvar): Detect defining a variable currently lexically bound
authorStefan Monnier <monnier@iro.umontreal.ca>
Wed, 25 Nov 2020 19:49:55 +0000 (14:49 -0500)
committerStefan Monnier <monnier@iro.umontreal.ca>
Wed, 25 Nov 2020 19:49:55 +0000 (14:49 -0500)
* src/eval.c (lexbound_p): New function.
(Finternal__define_uninitialized_variable): Use it.

etc/NEWS
src/eval.c

index 9091643da5a471381ad509c5bdf5dff2d37ff05a..d1e896ef70e453b91773be520c15869b1d38818c 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1806,6 +1806,11 @@ ledit.el, lmenu.el, lucid.el and old-whitespace.el.
 \f
 * Lisp Changes in Emacs 28.1
 
+---
+** `defvar` detects the error of defining a variable currently lexically bound.
+Such mixes are always signs that the outer lexical binding was an
+error and should have used dynamic binding instead.
+
 +++
 ** New completion function 'affixation-function' to add prefix/suffix.
 It accepts a list of completions and should return a list where
index 76708e6e7e2120c2233dbffe3b6dd2acc53ed70d..3f9dcf6be6d05c073b13ee396e92cc110ea05448 100644 (file)
@@ -691,6 +691,45 @@ default_toplevel_binding (Lisp_Object symbol)
   return binding;
 }
 
+/* Look for a lexical-binding of SYMBOL somewhere up the stack.
+   This will only find bindings created with interpreted code, since once
+   compiled names of lexical variables are basically gone anyway.  */
+static bool
+lexbound_p (Lisp_Object symbol)
+{
+  union specbinding *pdl = specpdl_ptr;
+  while (pdl > specpdl)
+    {
+      switch ((--pdl)->kind)
+       {
+       case SPECPDL_LET_DEFAULT:
+       case SPECPDL_LET:
+         if (EQ (specpdl_symbol (pdl), Qinternal_interpreter_environment))
+           {
+             Lisp_Object env = specpdl_old_value (pdl);
+             if (CONSP (env) && !NILP (Fassq (symbol, env)))
+               return true;
+           }
+         break;
+
+       case SPECPDL_UNWIND:
+       case SPECPDL_UNWIND_ARRAY:
+       case SPECPDL_UNWIND_PTR:
+       case SPECPDL_UNWIND_INT:
+       case SPECPDL_UNWIND_INTMAX:
+       case SPECPDL_UNWIND_EXCURSION:
+       case SPECPDL_UNWIND_VOID:
+       case SPECPDL_BACKTRACE:
+       case SPECPDL_LET_LOCAL:
+         break;
+
+       default:
+         emacs_abort ();
+       }
+    }
+  return false;
+}
+
 DEFUN ("default-toplevel-value", Fdefault_toplevel_value, Sdefault_toplevel_value, 1, 1, 0,
        doc: /* Return SYMBOL's toplevel default value.
 "Toplevel" means outside of any let binding.  */)
@@ -726,6 +765,15 @@ This is like `defvar' and `defconst' but without affecting the variable's
 value.  */)
   (Lisp_Object symbol, Lisp_Object doc)
 {
+  if (!XSYMBOL (symbol)->u.s.declared_special
+      && lexbound_p (symbol))
+    /* This test tries to catch the situation where we do
+       (let ((<foo-var> ...)) ...(<foo-function> ...)....)
+       and where the `foo` package only gets loaded when <foo-function>
+       is called, so the outer `let` incorrectly made the binding lexical
+       because the <foo-var> wasn't yet declared as dynamic at that point.  */
+    error ("Defining as dynamic an already lexical var");
+
   XSYMBOL (symbol)->u.s.declared_special = true;
   if (!NILP (doc))
     {