GNU Emacs 26.1 (build 2, i686-pc-linux-gnu, GTK+ Version 3.22.30) of 2018-05-29:
The problem is in byte-compile--reify-function
within byte-compile
.
In an edebug session byte-compile--reify-function
transforms
'(closure ((a . 1) (a . 0) t) nil a)
into
(lambda nil (let ((a (quote 1)) (a (quote 0))) a))
which is wrong.
(funcall '(closure ((a . 1) (a . 0) t) nil a))
gives 1 which shows that the lexical environment is looked up in the sense of assoc
, i.e., the first match wins.
Opposed to that, the last binding for a
is relevant in the let
-form.
Since it is let
and not let*
a simple fix would be to reverse the order of the bindings in the let
-form.
Note that such a simple fix works for your case with constant expressions but not for the general case where the expressions are evaluated themselves.
The evaluation order of the expressions is changed by the fix which can be disastrous.
The original version of byte-compile--reify-function
is as follows.
(defun byte-compile--reify-function (fun)
"Return an expression which will evaluate to a function value FUN.
FUN should be either a `lambda' value or a `closure' value."
(pcase-let* (((or (and `(lambda ,args . ,body) (let env nil))
`(closure ,env ,args . ,body))
fun)
(preamble nil)
(renv ()))
;; Split docstring and `interactive' form from body.
(when (stringp (car body))
(push (pop body) preamble))
(when (eq (car-safe (car body)) 'interactive)
(push (pop body) preamble))
;; Turn the function's closed vars (if any) into local let bindings.
(dolist (binding env)
(cond
((consp binding)
;; We check shadowing by the args, so that the `let' can be moved
;; within the lambda, which can then be unfolded. FIXME: Some of those
;; bindings might be unused in `body'.
(unless (memq (car binding) args) ;Shadowed.
(push `(,(car binding) ',(cdr binding)) renv)))
((eq binding t))
(t (push `(defvar ,binding) body))))
(if (null renv)
`(lambda ,args ,@preamble ,@body)
`(lambda ,args ,@preamble (let ,(nreverse renv) ,@body)))))
We fix it by removing the last nreverse
:
(defun byte-compile--reify-function (fun)
"Return an expression which will evaluate to a function value FUN.
FUN should be either a `lambda' value or a `closure' value."
(pcase-let* (((or (and `(lambda ,args . ,body) (let env nil))
`(closure ,env ,args . ,body))
fun)
(preamble nil)
(renv ()))
;; Split docstring and `interactive' form from body.
(when (stringp (car body))
(push (pop body) preamble))
(when (eq (car-safe (car body)) 'interactive)
(push (pop body) preamble))
;; Turn the function's closed vars (if any) into local let bindings.
(dolist (binding env)
(cond
((consp binding)
;; We check shadowing by the args, so that the `let' can be moved
;; within the lambda, which can then be unfolded. FIXME: Some of those
;; bindings might be unused in `body'.
(unless (memq (car binding) args) ;Shadowed.
(push `(,(car binding) ',(cdr binding)) renv)))
((eq binding t))
(t (push `(defvar ,binding) body))))
(if (null renv)
`(lambda ,args ,@preamble ,@body)
`(lambda ,args ,@preamble (let ,renv ,@body)))))
You can evaluate the last defun
in the *scratch*
buffer. Afterwards eval-last-sexp
of your first elisp snippet gives 1, as expected.
lexical-let
uses dynamic binding (it's a trick with renaming things behind the scenes), so it's expected that it would produce the same result as the "with lexical-binding off" result.lexical-binding
local prop-line and compile the file. I guess I'm beginning to understand whylexical-let
dates back 25 years at minimum.lexical-binding
was only introduced in Emacs 24.test-for-lexical-binding
error is due to the fix for Bug#12938.