Highlight non-local variables in Emacs Lisp

In js2 mode, global variables are automatically allocated for me:

enter image description here

How can I do the same in Emacs lisp? I would like 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 
+4
source share
3 answers

You can use the byte compiler by installing flycheck . Flycheck is an alternative to flymake and supports Elisp.

This will not help you in the first example, but it will be for the second (if require assumed):

 (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 secondary mode that almost exactly describes what is described in the question. Install it, then run hdefd-highlight-mode in the emacs-lisp-module buffer. You can then run the hdefd-cycle command until you see only variables that are not yet defined. This gives something like:

hl-defined screenshot

(This is not ideal, the hl-specific does not recognize that fn is a parameter that is not a free variable, and it confuses the list function with the parameter used here. However, it is very useful for the one used in the question.)

Finally, some packages include highlighting for the functions that they define. For example, dash.el provides highlighting for its functions, plus the names of the variables that it uses in anaphoric macros (i.e. it is highlighted).

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

I would say that it is quite a lot of work ...

The best way is to use font-lock-mode and add a new rule. Typically, rules contain a regular expression to match something, but it is also legal to use a function to do this. This function can then search for identifiers, and for each identifier it dolist , it can check whether it is bound locally by checking if the variable is present in the parameter list, in let , dolist or a similar construction.

An example package in which similar things are cwarn mode , which distinguish (among others) the purposes inside expressions for C-like languages.

+2
source

I suppose that it’s not even possible to pinpoint variable binding regions, or at least not to include, for example, a byte compiler (or parts of it) or reimplementation of parts of Emacs Lisp semantics.

The key problem is macros. They are not hygienic in Emacs Lisp. Thus, any macro can introduce arbitrary local bindings . In fact, many macros do this, such as the dolist , condition-case and pcase from the standard library, or the anaphoric list processing function from dash.el , to name a popular third-party library.

Using these macros, it becomes impossible to determine the scope of variables only from the syntactical context. 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)))) 

Unaware of the condition-case and --each , are err and it locally related? If so, in which sub-expressions are they related, for example. err is connected in all subexpressions or only with the form of the handler (the latter matters)?

To determine the scope of a variable in such cases, you either need to maintain an exhaustive white list of macros along with your binding properties, or you need to expand the macros to dynamically determine their binding properties (for example, look for let in the extended body).

Both of these approaches are a lot of work in their implementation and have disadvantages. The whitelist of macro definitions is almost naturally incomplete, incorrect, and obsolete (just look at the complex linking semantics of pcase ), while expanding macros requires a macro definition to be present, which is not always the case, for example, if you are editing Emacs Lisp using the above dash .el, without actually installing this library.

However, expanding macros is probably the best effort, and even better, you don't need to implement it yourself. The Emacs Lisp byte compiler already does this, and it warns of references to free variables, as well as lexical bindings also included in unused lexical variables. So, the byte compiles your files!

At best, avoid calling byte-compile-file from your Emacs startup and instead write the Makefile to the byte compiler in a new 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 $< 

For more complex libraries, use the -L flag for Emacs to configure the correct load-path for compilation.

+2
source

Source: https://habr.com/ru/post/1480973/


All Articles