1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-23 22:20:24 -08:00

Add aid for finding missing dynamic variable declarations

Find lexical use of variables that are dynamically declared in other
files by recording 'defvar' declarations in files that can be read
in by the compiler in a second compilation.  This is particularly
useful when converting code to use lexical-binding.

The facility is controlled by setting environment variables:

 EMACS_GENERATE_DYNVARS -- set to non-empty to generate a .dynvars file
                           corresponding to each .elc.
 EMACS_DYNVARS_FILE     -- set to the name of a .dynvars file to use
                           as defvar information during compilation,
                           enabling the new warnings.

* lisp/emacs-lisp/bytecomp.el (byte-compile--known-dynamic-vars)
(byte-compile--seen-defvars): New variables.
(byte-compile-warning-types): Add lexical-dynamic warning.
(byte-compile--load-dynvars, byte-compile--warn-lexical-dynamic):
New functions.
* lisp/emacs-lisp/bytecomp.el (byte-compile-file, byte-compile--declare-var)
(byte-compile-lambda, byte-compile-bind): Add dynamic variable loads,
dumps and checks.
* doc/lispref/variables.texi (Converting to Lexical Binding): Document.
This commit is contained in:
Mattias Engdegård 2020-10-16 19:02:25 +02:00
parent 39a001451f
commit 3217ae6e05
2 changed files with 90 additions and 2 deletions

View file

@ -268,6 +268,13 @@ This option is enabled by default because it reduces Emacs memory usage."
(defconst byte-compile-log-buffer "*Compile-Log*"
"Name of the byte-compiler's log buffer.")
(defvar byte-compile--known-dynamic-vars nil
"Variables known to be declared as dynamic, for warning purposes.
Each element is (VAR . FILE), indicating that VAR is declared in FILE.")
(defvar byte-compile--seen-defvars nil
"All dynamic variable declarations seen so far.")
(defcustom byte-optimize-log nil
"If non-nil, the byte-compiler will log its optimizations.
If this is `source', then only source-level optimizations will be logged.
@ -290,7 +297,7 @@ The information is logged to `byte-compile-log-buffer'."
(defconst byte-compile-warning-types
'(redefine callargs free-vars unresolved
obsolete noruntime cl-functions interactive-only
make-local mapcar constants suspicious lexical)
make-local mapcar constants suspicious lexical lexical-dynamic)
"The list of warning types used when `byte-compile-warnings' is t.")
(defcustom byte-compile-warnings t
"List of warnings that the byte-compiler should issue (t for all).
@ -310,6 +317,8 @@ Elements of the list may be:
interactive-only
commands that normally shouldn't be called from Lisp code.
lexical global/dynamic variables lacking a prefix.
lexical-dynamic
lexically bound variable declared dynamic elsewhere
make-local calls to make-variable-buffer-local that may be incorrect.
mapcar mapcar called for effect.
constants let-binding of, or assignment to, constants/nonvariables.
@ -1873,6 +1882,17 @@ If compilation is needed, this functions returns the result of
(load (if (file-exists-p dest) dest filename)))
'no-byte-compile)))
(defun byte-compile--load-dynvars (file)
(and file (not (equal file ""))
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(let ((vars nil)
var)
(while (ignore-errors (setq var (read (current-buffer))))
(push var vars))
vars))))
(defvar byte-compile-level 0 ; bug#13787
"Depth of a recursive byte compilation.")
@ -1911,6 +1931,9 @@ The value is non-nil if there were no errors, nil if errors."
(let ((byte-compile-current-file filename)
(byte-compile-current-group nil)
(set-auto-coding-for-load t)
(byte-compile--seen-defvars nil)
(byte-compile--known-dynamic-vars
(byte-compile--load-dynvars (getenv "EMACS_DYNVARS_FILE")))
target-file input-buffer output-buffer
byte-compile-dest-file)
(setq target-file (byte-compile-dest-file filename))
@ -2035,6 +2058,15 @@ The value is non-nil if there were no errors, nil if errors."
filename))))
(save-excursion
(display-call-tree filename)))
(let ((gen-dynvars (getenv "EMACS_GENERATE_DYNVARS")))
(when (and gen-dynvars (not (equal gen-dynvars ""))
byte-compile--seen-defvars)
(let ((dynvar-file (concat target-file ".dynvars")))
(message "Generating %s" dynvar-file)
(with-temp-buffer
(dolist (var (delete-dups byte-compile--seen-defvars))
(insert (format "%S\n" (cons var filename))))
(write-region (point-min) (point-max) dynvar-file)))))
(if load
(load target-file))
t))))
@ -2425,7 +2457,8 @@ list that represents a doc string reference.
(setq byte-compile-lexical-variables
(delq sym byte-compile-lexical-variables))
(byte-compile-warn "Variable `%S' declared after its first use" sym))
(push sym byte-compile-bound-variables))
(push sym byte-compile-bound-variables)
(push sym byte-compile--seen-defvars))
(defun byte-compile-file-form-defvar (form)
(let ((sym (nth 1 form)))
@ -2831,6 +2864,16 @@ If FORM is a lambda or a macro, byte-compile it as a function."
(ash nonrest 8)
(ash rest 7)))))
(defun byte-compile--warn-lexical-dynamic (var context)
(when (byte-compile-warning-enabled-p 'lexical-dynamic var)
(byte-compile-warn
"`%s' lexically bound in %s here but declared dynamic in: %s"
var context
(mapconcat #'identity
(mapcan (lambda (v) (and (eq var (car v))
(list (cdr v))))
byte-compile--known-dynamic-vars)
", "))))
(defun byte-compile-lambda (fun &optional add-lambda reserved-csts)
"Byte-compile a lambda-expression and return a valid function.
@ -2859,6 +2902,10 @@ for symbols generated by the byte compiler itself."
(if (cdr body)
(setq body (cdr body))))))
(int (assq 'interactive body)))
(when lexical-binding
(dolist (var arglistvars)
(when (assq var byte-compile--known-dynamic-vars)
(byte-compile--warn-lexical-dynamic var 'lambda))))
;; Process the interactive spec.
(when int
(byte-compile-set-symbol-position 'interactive)
@ -4379,6 +4426,8 @@ Return non-nil if the TOS value was popped."
;; VAR is a simple stack-allocated lexical variable.
(progn (push (assq var init-lexenv)
byte-compile--lexical-environment)
(when (assq var byte-compile--known-dynamic-vars)
(byte-compile--warn-lexical-dynamic var 'let))
nil)
;; VAR should be dynamically bound.
(while (assq var byte-compile--lexical-environment)