Before you read the answer, the problem and the answer are only helpful for those want to explicitly call the byte compiler and want to learn the detail on how lexical binding and dynamic binding interact with each other in Emacs. Simply adhering to the usually coding convention can completely avoid the situation.
On 29.1, the minimal reproduced example should be like
(defvar foo) ;; foo must be fresh and unbound to reproduce the problem.
(let ()
(lambda () (setq foo t)))
(closure (foo t) nil (setq foo t))
(let (_)
(lambda () (setq foo t)))
(closure (t) nil (setq foo t)) ;; Warn about free variable if compiled.
They produce different closure object thus byte compiler produce different messages.
The difference of results in 29.1 is likely due to
(let (_) ... )
has the semantic of introducing a new scope (mentioned in the manual but didn't into any of the detail of this), thus it would nullify the (defvar ...
declaration in global scope. The closure produced reflected this.
It can thus conclude that it is expected for the byte compiler to produce a warning here.
There are a few edge case behavior (tested with 29.1) worth to be mentioned: defvar
does not need to be evaluated to take effect, and does not seem to produced any side effect when evaluated if no binding is provided. The interpreter and compiler would search the defvar
declarations in lexical binding mode to handle their effect. So simply comment/uncomment defvar
declarations in top level of the scratch buffer without reevaluate it can affect the byte code produced. And there are too many ways fool the interpreter/compiler by hide the defvar
declarations from them.
defconst
works in a similar way to defvar
.
However, once used defvar
/setq
/defconst
to binding the variable, the variable itself does become "permanently" special
and makunbound
can not revert the effect, but unintern
can.
Remember that, (defvar foo)
and (defvar foo nil)
has
different meaning and the behavior can be different.
On 28.2 the result is also different, although the byte compiler would not generate a complain for the free variable. Whether foo
is bound or undeclared would not make a difference to the result.
Op mentioned the FIXME comment in dlet
. However, that is not a bug
at all. The problem mentioned in dlet
is because the byte
compiler would optimize
(let () body) => (progn body)
which is a behavior decided before lexical scoping was introduced. Thus the verbose (let (_) ...)
is used.
The (let (_) body)
expression itself is not a magic, it is from (let (a) body)
where a
is not used in body
, the only difference is byte compiler would warn if a
is an unused variable, and might produce more efficient byte code for _
version. Actually you can use _a
to nullify the unused variable warning and it would produce the same bytecode.
(lambda ()
(let (_)
(defvar abc-foo)
(let (abc-foo)
(setq abc-foo 1))))
(lambda ()
(let (a)
(defvar abc-foo)
(let (abc-foo)
(setq abc-foo 1))))
(lambda ()
(let (_a)
(defvar abc-foo)
(let (abc-foo)
(setq abc-foo 1))))
The only magical thing is actually defvar
:
(equal
(byte-compile
(lambda ()
(progn
(defvar abc-foo)
(let (abc-foo)
(setq abc-foo 1)))))
(byte-compile
(lambda ()
(let (_) ;; with or without _ produced same result
(defvar abc-foo)
(let (abc-foo)
(setq abc-foo 1))))))
;; t
dlet
? Perhaps they fix this bug but forgot to update the comment.-Q
?defvar
a SYMBOL without VALUE to make that symbol special locally." I alreadydefvar
ed it and seems that thedefvar
form and thelambda
form are in the same lexical scope.