indicate to the compiler that this is a variable known not to
be used.)
+@subsubheading Cross-file variable checking
+
+@strong{Note:} This is an experimental feature that may change or
+disappear without prior notice.
+
+The byte-compiler can also warn about lexical variables that are
+special in other Emacs Lisp files, often indicating a missing
+@code{defvar} declaration. This useful but somewhat specialised check
+requires three steps:
+
+@enumerate
+@item
+Byte-compile all files whose special variable declarations may be of
+interest, with the environment variable @env{EMACS_GENERATE_DYNVARS}
+set to a nonempty string. These are typically all the files in the
+same package or related packages or Emacs subsystems. The process
+will generate a file whose name ends in @file{.dynvars} for each
+compiled Emacs Lisp file.
+
+@item
+Concatenate the @file{.dynvars} files into a single file.
+
+@item
+Byte-compile the files that need to be checked, this time with
+the environment variable @env{EMACS_DYNVARS_FILE} set to the name
+of the aggregated file created in step 2.
+@end enumerate
+
+Here is an example illustrating how this could be done, assuming that
+a Unix shell and @command{make} are used for byte-compilation:
+
+@example
+$ rm *.elc # force recompilation
+$ EMACS_GENERATE_DYNVARS=1 make # generate .dynvars
+$ cat *.dynvars > ~/my.dynvars # combine .dynvars
+$ rm *.elc # force recompilation
+$ EMACS_DYNVARS_FILE=~/my.dynvars make # perform checks
+@end example
+
@node Buffer-Local Variables
@section Buffer-Local Variables
@cindex variable, buffer-local
(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.
(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).
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.
(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.")
(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))
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))))
(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)))
(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.
(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)
;; 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)