]> git.eshelyaron.com Git - emacs.git/commitdiff
Warn when loading .el files without lexical-binding declaration
authorMattias EngdegÄrd <mattiase@acm.org>
Fri, 3 May 2024 15:58:44 +0000 (17:58 +0200)
committerEshel Yaron <me@eshelyaron.com>
Mon, 6 May 2024 16:37:52 +0000 (18:37 +0200)
This corresponds to the byte-compiler warning for the same issue,
here emitted for files that aren't compiled but loaded from source.
It should make the planned change to enable lexical binding by default
in Emacs 31 go smoother.

* src/lread.c (lexical_cookie_t): New type.
(lisp_file_lexically_bound_p): Renamed to...
(lisp_file_lexical_cookie): ...this, with the return value retyped.
* src/lread.c (warn_missing_cookie): New.
(Fload): Warn when loading source file and cookie missing.
(Feval_buffer): Add LOADING arg; warn when set and cookie missing.
* lisp/international/mule.el (load-with-code-conversion):
* lisp/startup.el (command-line--load-script):
Call eval-buffer with LOADING arg set.
* etc/NEWS: Announce.

(cherry picked from commit 81bad84a617be38459da313a75719b5627bb15fe)

etc/NEWS
lisp/international/mule.el
lisp/startup.el
src/lread.c

index e068497d7bab8d922ddc18f2c06a6ab7ed18c957..010e80fb93b172f50d9bdbea72b9a867afef445a 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2571,6 +2571,13 @@ The warning name is 'docstrings-control-chars'.
 *** The warning about wide docstrings can now be disabled separately.
 Its warning name is 'docstrings-wide'.
 
+---
+** Warn about missing 'lexical-binding' directive when loading .el files.
+Emacs now emits a run-time warning if an Elisp source file being loaded
+lacks the '-*- lexical-binding: ... -*-' cookie on the first line.
+See the lexical-binding compiler warning described above for how to make
+the warning go away.
+
 ---
 ** New user option 'native-comp-async-warnings-errors-kind'.
 It allows control of what kinds of warnings and errors from asynchronous
index a17221e6d2165655889f06e4ba0a88221c675ec4..8875c4f06afc55c61a26475e6286a5f69671de9a 100644 (file)
@@ -367,7 +367,7 @@ Return t if file exists."
                 (eval-buffer buffer nil
                             ;; This is compatible with what `load' does.
                              (if dump-mode file fullname)
-                            nil t))))
+                            nil t t))))
        (let (kill-buffer-hook kill-buffer-query-functions)
          (kill-buffer buffer)))
       (do-after-load-evaluation fullname)
index 357a4154e4c870ab5cae4b99dd19ba6b09ffa71d..f2532f5254e3f0e4027aa9080c09cf790b786e4a 100644 (file)
@@ -2935,7 +2935,7 @@ nil default-directory" name)
        ;; buffer is empty.
        (when (looking-at "#!")
          (delete-line))
-       (eval-buffer buffer nil file nil t)))))
+       (eval-buffer buffer nil file nil t t)))))
 
 (defun command-line--eval-script (file)
   (load-with-code-conversion
index 983fdb883ffbbcdad20d11d899ea4dc0e26e7a44..f9a1a8562cb8a7f344a2f1df157d82561e0a945a 100644 (file)
@@ -1053,13 +1053,19 @@ DEFUN ("get-file-char", Fget_file_char, Sget_file_char, 0, 0, 0,
 
 \f
 
-/* Return true if the lisp code read using READCHARFUN defines a non-nil
-   `lexical-binding' file variable.  After returning, the stream is
-   positioned following the first line, if it is a comment or #! line,
-   otherwise nothing is read.  */
-
-static bool
-lisp_file_lexically_bound_p (Lisp_Object readcharfun)
+typedef enum {
+  Cookie_None,                 /* no cookie */
+  Cookie_Dyn,                  /* explicit dynamic binding */
+  Cookie_Lex                   /* explicit lexical binding */
+} lexical_cookie_t;
+
+/* Determine if the lisp code read using READCHARFUN defines a
+   `lexical-binding' file variable return its value.
+   After returning, the stream is positioned following the first line,
+   if it is a comment or #! line, otherwise nothing is read.  */
+
+static lexical_cookie_t
+lisp_file_lexical_cookie (Lisp_Object readcharfun)
 {
   int ch = READCHAR;
 
@@ -1070,7 +1076,7 @@ lisp_file_lexically_bound_p (Lisp_Object readcharfun)
         {
           UNREAD (ch);
           UNREAD ('#');
-          return 0;
+          return Cookie_None;
         }
       while (ch != '\n' && ch != EOF)
         ch = READCHAR;
@@ -1083,12 +1089,12 @@ lisp_file_lexically_bound_p (Lisp_Object readcharfun)
     /* The first line isn't a comment, just give up.  */
     {
       UNREAD (ch);
-      return 0;
+      return Cookie_None;
     }
   else
     /* Look for an appropriate file-variable in the first line.  */
     {
-      bool rv = 0;
+      lexical_cookie_t rv = Cookie_None;
       enum {
        NOMINAL, AFTER_FIRST_DASH, AFTER_ASTERIX
       } beg_end_state = NOMINAL;
@@ -1170,7 +1176,7 @@ lisp_file_lexically_bound_p (Lisp_Object readcharfun)
              if (strcmp (var, "lexical-binding") == 0)
                /* This is it...  */
                {
-                 rv = (strcmp (val, "nil") != 0);
+                 rv = strcmp (val, "nil") != 0 ? Cookie_Lex : Cookie_Dyn;
                  break;
                }
            }
@@ -1336,6 +1342,17 @@ close_file_unwind_android_fd (void *ptr)
 
 #endif
 
+static void
+warn_missing_cookie (Lisp_Object file)
+{
+  Lisp_Object msg = CALLN (Fformat,
+                          build_string ("File %s lacks `lexical-binding'"
+                                        " directive on its first line"),
+                          file);
+  Vdelayed_warnings_list = Fcons (list2 (Qlexical_binding, msg),
+                                 Vdelayed_warnings_list);
+}
+
 DEFUN ("load", Fload, Sload, 1, 5, 0,
        doc: /* Execute a file of Lisp code named FILE.
 First try FILE with `.elc' appended, then try with `.el', then try
@@ -1785,7 +1802,10 @@ Return t if the file exists and loads successfully.  */)
     }
   else
     {
-      if (lisp_file_lexically_bound_p (Qget_file_char))
+      lexical_cookie_t lc = lisp_file_lexical_cookie (Qget_file_char);
+      if (lc == Cookie_None && !compiled)
+       warn_missing_cookie (file);
+      if (lc == Cookie_Lex)
         Fset (Qlexical_binding, Qt);
 
       if (! version || version >= 22)
@@ -2618,7 +2638,7 @@ readevalloop (Lisp_Object readcharfun,
   unbind_to (count, Qnil);
 }
 
-DEFUN ("eval-buffer", Feval_buffer, Seval_buffer, 0, 5, "",
+DEFUN ("eval-buffer", Feval_buffer, Seval_buffer, 0,6, "",
        doc: /* Execute the accessible portion of current buffer as Lisp code.
 You can use \\[narrow-to-region] to limit the part of buffer to be evaluated.
 When called from a Lisp program (i.e., not interactively), this
@@ -2635,6 +2655,8 @@ UNIBYTE, if non-nil, specifies `load-convert-to-unibyte' for this
 DO-ALLOW-PRINT, if non-nil, specifies that output functions in the
  evaluated code should work normally even if PRINTFLAG is nil, in
  which case the output is displayed in the echo area.
+LOADING, if non-nil, indicates that this call is part of loading a
+Lisp source file.
 
 This function ignores the current value of the `lexical-binding'
 variable.  Instead it will heed any
@@ -2643,7 +2665,8 @@ settings in the buffer, and if there is no such setting, the buffer
 will be evaluated without lexical binding.
 
 This function preserves the position of point.  */)
-  (Lisp_Object buffer, Lisp_Object printflag, Lisp_Object filename, Lisp_Object unibyte, Lisp_Object do_allow_print)
+  (Lisp_Object buffer, Lisp_Object printflag, Lisp_Object filename,
+   Lisp_Object unibyte, Lisp_Object do_allow_print, Lisp_Object loading)
 {
   specpdl_ref count = SPECPDL_INDEX ();
   Lisp_Object tem, buf;
@@ -2667,7 +2690,10 @@ This function preserves the position of point.  */)
   specbind (Qstandard_output, tem);
   record_unwind_protect_excursion ();
   BUF_TEMP_SET_PT (XBUFFER (buf), BUF_BEGV (XBUFFER (buf)));
-  specbind (Qlexical_binding, lisp_file_lexically_bound_p (buf) ? Qt : Qnil);
+  lexical_cookie_t lc = lisp_file_lexical_cookie (buf);
+  if (!NILP (loading) && lc == Cookie_None)
+    warn_missing_cookie (filename);
+  specbind (Qlexical_binding, lc == Cookie_Lex ? Qt : Qnil);
   BUF_TEMP_SET_PT (XBUFFER (buf), BUF_BEGV (XBUFFER (buf)));
   readevalloop (buf, 0, filename,
                !NILP (printflag), unibyte, Qnil, Qnil, Qnil);