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.