This answer is an extension of Phils answer.
Your question shows the potential danger of locally binding special-declared variables.
The convention of prefixing symbol names with library names normally protects us from accidentally using special-declared symbols for local bindings that should have lexical scope.
But there are exceptions like displayed-month and displayed-year from calendar.el.
One can ensure lexical binding with the help of uninterned symbols.
The principle is described through a simple documented example:
;; -*- lexical-binding: t -*- (require 'cl) ;; reset: (unintern 'wtf) (unintern 'foo) ;; declare `wtf' as special: (defvar wtf) (setq wtf 0) (defmacro f () (let ((sym (make-symbol "wtf"))) ;; `make-symbol' returns an uninterned symbol with name `wtf' `(let ((,sym 1)) ;; We substitute here the uninterned symbol `wtf'. (lambda () ,sym)))) (setq ans (funcall (f))) ;; => 1
It is clear that the definition of the macro f in the above example is cumbersome.
But a special lexlet macro as defined in the following can relieve us from that work.
First comes the library code defining the lexlet macro and below that the user code resembling the simplified introductory example.
The macro lexlet generates uninterned duplicates of all locally bound symbols, locally binds the uninterned symbols to their given values and replaces the locally bound interned symbols in the body with their uninterned counterparts.
The definition of the macro is kept simple. But there is a small caveat. One cannot use the function definition of the locally bound symbols.
;; -*- lexical-binding: t; -*- ;; Here starts the library code. (defun lexlet--search (data symbol-alist) "Replace symbols according to SYMBOL-ALIST in DATA." (cond ((symbolp data) (or (alist-get data symbol-alist) data)) ((vectorp data) (apply #'vector (seq-map #'lexlet--search data symbol-alist))) ((consp data) (cons (lexlet--search (car data) symbol-alist) (lexlet--search (cdr data) symbol-alist))) (t data))) ;; Test: ;; (lexlet--search '(lambda () wtf) '((wtf . var))) (defmacro lexlet (bindings &rest body) "Like `let' but with lexical binding for all symbols in BINDINGS. If a symbol has a local binding in BINDINGS its binding during the execution of BODY is lexical even if the symbol is declared as special. This macro requires `lexical-binding' set to t." (declare (indent 1) (debug ((&rest (symbol sexp)) body))) (let* ((symbol-alist (mapcar (lambda (binding) (let ((symbol (car binding))) (cons symbol (make-symbol (symbol-name symbol))))) bindings)) (new-bindings (mapcar (lambda (binding) (let* ((old-symbol (car binding)) (new-symbol (alist-get old-symbol symbol-alist))) (cons new-symbol (cdr binding)))) bindings))) (append (list #'let new-bindings) (lexlet--search body symbol-alist)))) (provide 'lexlet)
With lexlet the user code of the introductory example looks friendlier:
(require 'lexlet) (defvar wtf) (setq wtf 0) (defun f () (lexlet ((wtf 1)) (lambda () wtf))) (setq ans (funcall (f))) ;; => 1
It is important that the macro extends the lexical environment created by an outer let.
For an example eval with an environment given as argument lexical does not extend an outer lexical environment and is therefore only of limited use for our purpose.
But, lexlet is okay in that regard:
;; -*- lexical-binding: t -*- (require 'lexlet) ;; reset: (unintern 'wtf) (unintern 'lexbound) (defvar wtf) (setq wft 0) (defun f () (let ((lexbound 1)) (lexlet ((wtf 2)) (lambda () (list lexbound wtf))))) (setq ans (funcall (f))) ;; => (1 2)
fset. The enclosing lambda can be defined andfsetby adefunas in your case. But you are right, the behavior is wired.lambdais self-quoting. Furthermore, for repeatability it is better to separate the marking of the symbol as special and the binding of the symbol to a value:(defvar wtf) (setq wtf 10).