diff options
author | Mattias EngdegÄrd <mattiase@acm.org> | 2020-10-16 19:02:25 +0200 |
---|---|---|
committer | Mattias EngdegÄrd <mattiase@acm.org> | 2020-10-17 16:57:38 +0200 |
commit | 3217ae6e05c5d99f5d2d364b8f631001ee1d29c9 (patch) | |
tree | f168d63122d599d493f35b0e6f3388ff14972a23 /lisp/emacs-lisp | |
parent | 39a001451fba253bf9480d1a0347ae054760f8e2 (diff) | |
download | emacs-3217ae6e05c5d99f5d2d364b8f631001ee1d29c9.tar.gz emacs-3217ae6e05c5d99f5d2d364b8f631001ee1d29c9.tar.bz2 emacs-3217ae6e05c5d99f5d2d364b8f631001ee1d29c9.zip |
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.
Diffstat (limited to 'lisp/emacs-lisp')
-rw-r--r-- | lisp/emacs-lisp/bytecomp.el | 53 |
1 files changed, 51 insertions, 2 deletions
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index f4b9139ef1d..90809a929b9 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -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) |