简体   繁体   中英

Highlight non-local variables in Emacs Lisp

In js2-mode, global variables are automatically highlighted for me:

在此输入图像描述

How can I do the same in Emacs lisp? I'd like to be able to highlight flymake-log-level and barr in the following:

(defun foo ()
  (let (bar baz)
    (setq baz flymake-log-level) ;; flymake-log-level isn't locally bound
    (setq barr (1+ flymake-log-level)))) ;; misspelled bar

It's possible to take advantage of the byte-compiler by installing flycheck . Flycheck is an alternative to flymake and has support for Elisp.

It won't help you for the first example, but it will for the second (assuming the necessary require is present):

(require 'flymake)

(defun foo ()
  (let (bar baz)
    (setq baz flymake-log-level) ;; no complaints here
    (setq barr (1+ flymake-log-level)))) ;; assignment to free variable `barr'

There's also hl-defined.el , a minor mode that does almost exactly what is described in the question. Install it, then run hdefd-highlight-mode in an emacs-lisp-mode buffer. You can then run the command hdefd-cycle until you are only showing variables that aren't already defined. This gives something like:

hl定义的截图

(This isn't perfect, hl-defined doesn't recognise that fn is a parameter not a free variable, and it confuses the function list with the parameter used here. Still, it's very helpful for the use case described in the question.)

Finally, some packages include highlighting for the functions they define. For example, dash.el provides highlighting for its functions, plus the variable names it uses in anaphoric macros (ie it highlights it ).

;; Enable syntax highlighting of dash functions
(eval-after-load "dash" '(dash-enable-font-lock))

I would say that this is quite a bit of work...

The best way is to use font-lock-mode and add a new rule. Normally, rules contains a regexp to match something, however, it is also legal to use a function to do this. This function could then search for identifiers, and for each identifier it finds it could check if it's bound locally by checking if the variable occurs in the parameter list, in a let , dolist or similar construct.

An example of a package that similar things is cwarn mode , which highlight (among else) assignments inside expressions, for C-like languages.

I presume that it is even impossible to accurately highlight variable binding scopes, or at least not without actually involving the byte compiler (or parts thereof), or re-implementing parts of Emacs Lisp' semantics.

The key problem are macros. These are not hygienic in Emacs Lisp. Thus any macro can introduce arbitrary local bindings . In fact, many macros do so, for instance dolist , condition-case and pcase from the standard library, or the anaphoric list processing functions from dash.el , to name a popular 3rd party library..

With these macros, it becomes impossible to determine the variable scoping from the syntactic context only. Take the following example:

(condition-case err
    (--each my-fancy-list
      (my-fancy-function it nil t))
  (error (message "Error %S happened: %s" (car err) (cadr err))))

Without knowing about condition-case and --each , are err and it locally bound? If so, in which sub-expressions are they bound, eg is err bound in all sub-expressions, or just the handler form only (the latter is the case)?

To determine variable scope in such cases, you either need to maintain an exhaustive whitelist of macros along with their binding properties, or you need to expand macros to determine their binding properties dynamically (eg look for let in the expanded body).

Both of these approaches amount to a lot of work when implemented, and have shortcomings. A whitelist of macro definitions is almost naturally incomplete, incorrect and outdated (just look at the complex binding semantics of pcase ), while expanding macros requires the macro definition to be present, which is not always the case, for instance if you are editing Emacs Lisp using the aforementioned dash.el, without having this library installed actually.

Still, expanding macros is probably the best effort, and even better, you don't need to implement it your own. The Emacs Lisp byte compiler already does this, and it warns about references to free variables, and with lexical binding enabled also about unused lexical variables. So, byte compile your files!

At best, avoid calling byte-compile-file from within your running Emacs, and instead write a Makefile to byte compile in a fresh Emacs instance, to have a clean environment:

SRCS = foo.el
OBJECTS = $(SRCS:.el=.elc)

.PHONY: compile
compile : $(OBJECTS)

%.elc : %.el
    $(EMACS) -Q --batch -f batch-byte-compile $<

In more complicated libraries, use the -L flag to Emacs to set up a proper load-path for compilation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM