[gnu.emacs.bug] Lambda expressions in function positions crash the byte-compiler

jourdan@MINOS.INRIA.FR (11/10/89)

Dear GNU Emacs developpers,

This is to report a bug in Emacs' byte compiler.  I followed all the
instructions in the "Bugs" section of Emacs' on-line manual (info), so
I am pretty sure this IS a bug.

Setting: GNU Emacs 18.55 compiled and running on a Sun-3/60 under
SunOS 3.4 ("berkeley-unix", says M-x emacs-version) and X11R3.

The problem is reproducible and does not depend on my .emacs file.  Is
is not linked to any visited file.

* HOW TO REPRODUCE THE BUG

Go to the *scratch* buffer (or any Lisp Interaction buffer).  Make
sure the byte-compiler is loaded (M-x load-library bytecomp).  Then
type (lines to type have a leading % to strip):

%(defun truc (z)
%  ((lambda (x) (+ x x)) (* z z)))^J
truc

%(truc 2)^J
8

%(symbol-function 'truc)^J
(lambda (z) ((lambda (x) (+ x x)) (* z z)))

%(byte-compile 'truc)

This produces a Lisp error; here is the backtrace:

Signalling: (wrong-type-argument symbolp (lambda (x) (+ x x)))
  byte-code("F	\"	G=
  byte-compile-form(((lambda (x) (+ x x)) (* z z)))
  byte-code("?
  byte-compile-body((((lambda (x) (+ x x)) (* z z))))
  byte-code("AA!" [form byte-compile-body] 2)
  byte-compile-progn((progn ((lambda (x) (+ x x)) (* z z))))
  funcall(byte-compile-progn (progn ((lambda (x) (+ x x)) (* z z))))
  byte-code("F	\"	G=
  byte-compile-form((progn ((lambda (x) (+ x x)) (* z z))))
  byte-code("AAMMMAAAAAN\n	O!	\n\n@	P\nA!			U
  byte-compile-top-level((progn ((lambda (x) (+ x x)) (* z z))))
  byte-code("	ADEA\"FA@;
  byte-compile-lambda((lambda (z) ((lambda (x) (+ x x)) (* z z))))
  byte-code("A!
  byte-compile(truc)
  eval-region(858 878 #<buffer *scratch*>)
  byte-code("BCD E
  eval-print-last-sexp(nil)
  call-interactively(eval-print-last-sexp)

The same bug occurs in the same conditions when I use the interpreted
version of the byte compiler (i.e., after having executed M-x
load-file /usr/local/GNU/emacs-18.55/lisp/bytecomp.el).  Here is the
corresponding backtrace:

Signalling: (wrong-type-argument symbolp (lambda (x) (+ x x)))
  get((lambda (x) (+ x x)) byte-compile)
  (let ...)
  (cond ...)
  byte-compile-form(((lambda (x) (+ x x)) (* z z)))
  (while ...)
  (if ...)
  byte-compile-body((((lambda (x) (+ x x)) (* z z))))
  byte-compile-progn((progn ((lambda (x) (+ x x)) (* z z))))
  funcall(byte-compile-progn (progn ((lambda (x) (+ x x)) (* z z))))
  (if ...)
  (let ...)
  (cond ...)
  byte-compile-form((progn ((lambda (x) (+ x x)) (* z z))))
  (let ...)
  byte-compile-top-level((progn ((lambda (x) (+ x x)) (* z z))))
  (list ...computing arguments...)
  (setq ...)
  (let* ...)
  byte-compile-lambda((lambda (z) ((lambda (x) (+ x x)) (* z z))))
  (fset ...computing arguments...)
  (if ...)
  byte-compile(truc)
  eval-region(3393 3413 #<buffer *scratch*>)
  byte-code("BCD E
  eval-print-last-sexp(nil)
  call-interactively(eval-print-last-sexp)

Of course the same error occurs if you put the definition of "truc" in
a file and try to byte-compile this file (this is how I actually
stumbled on the bug).

* MY COMMENTS ON THE BUG

My interpretation of the bug is that the interpreter allows
lambda-expressions in function positions, i.e., as the first element
of a list to evaluate, as said in the Emacs Lisp manual (section
3.2.2), but that, for some unknown reason (I can program in Lisp but I
don't know how to compile Lisp!), the byte compiler crashes on such
forms.  I suspect that it actually crashes on every list in function
position, but I don't know whether it can validly be something else
than a lambda-expression (even when interpreted, I mean).

This behaviour is, IMHO, *not* acceptable.  I could accept that the
compiler leaves those forms untouched for the interpreter to process
--although they are equivalent to "let" constructs, which the compiler
knows how to compile--, but I certainly cannot accept that it crashes
or produces incorrect code.

* MORE INFORMATION ON HOW I DISCOVERED THE BUG AND A NOT-QUITE-RELATED
BUT RELEVANT SUGGESTION

I stumbled on the bug while reworking "x-mouse.el".  I bind to my
mouse-clicks a number of functions which have different effects
according to the position of the mouse (in the "body" of the window,
in the "right margin" or in the "title bar", a.k.a. mode line).  For
instance, when I click the left button with no modifiers, I set the
point if the mouse is in the window, I put the line under the mouse at
the top of the window if the mouse is in the right margin, or I scroll
one page up if the mouse is in the bar.  This behaviour (and the
corresponding code) was stolen from that of Emacs running under
Emacstool.

All these functions have the same structure: select the window under
the mouse, determine in which of the three "parts" (body, margin or
bar) it actually is and do something according to this part.  So I
imagined I could concentrate the common structure into a generic
function or a macro, to which I pass the three functions corresponding
to what to do in the three parts of the window.  First I wrote a
function, then I wrote a macro producing a function definition, then I
wrote a macro producing the body of a function definition.  Here is
the latter:

(defmacro x-mouse-generic-macro (win-fn marg-fn bar-fn)
  "Macro to create the body of functions for handling x-mouse button presses.
Must be used as:
	(defun my-function (arg)
	  \"the doc\"
	  (x-mouse-generic-macro win-fn marg-fn bar-fn))
The argument of the function being defun'ed MUST be called \"arg\".
\"win-fn\" is the function to call when the pointer is really in a window,
with arguments the X and Y relative coordinates of the pointer in the window.
\"marg-fn\" is the function to call when the pointer is in the right margin,
with argument the Y relative coordinate of the pointer in the margin.
\"bar-fn\" is the function to call when the pointer is in the title bar,
with argument the X relative coordinate of the pointer in the bar.
First the window in which the pointer is is selected.  Then, according
to the position of the pointer, one of the functions is called, with
the corresponding argument(s).
The \"functions\" may be function symbols or lambda expressions."
  (` (let ((relative-coordinate (x-mouse-select arg)))
       (if relative-coordinate
	   (let ((x (car relative-coordinate))
		 (y (car (cdr relative-coordinate))))
	     (if (PhD-x-mouse-in-right-margin x y)
		 ((, marg-fn) y)
	       ((, win-fn) x y)))
	 (x-mouse-select (list (car arg) (1- (car (cdr arg)))))
	 ((, bar-fn) (car arg))))))

and an example of use:

(defun PhD-x-mouse-set-point (arg)
  "Select Emacs window mouse is on, and move point to mouse position.
Or if in simulated scroll-bar, scroll up.
Or if in simulated right margin, this line to top."
  (x-mouse-generic-macro
   (lambda (x y)			; win-fn
     (move-to-window-line y)
     (move-to-column (+ x (current-column))))
   scroll-up				; marg-fn
   (lambda (x)				; bar-fn
     (scroll-up nil))))

When interpreted, this works fine, even with lambda expressions as
functions, but the compiler fails to compile that.

My suggestion is related to the second trial I made, using a macro
producing a defun.  I do not show the macro itself but an example of
its use:

(x-mouse-generic-defun
 PhD-x-mouse-set-point
 "Select Emacs window mouse is on, and move point to mouse position.
Or if in simulated scroll-bar, scroll up.
Or if in simulated right margin, this line to top."
 (lambda (x y)				; win-fn
   (move-to-window-line y)
   (move-to-column (+ x (current-column))))
 scroll-up				; marg-fn
 (lambda (x)				; bar-fn
   (scroll-up nil)))

I find this more elegant than the previous version and it should be
strictly equivalent (even more efficient, since the expansion of the
macro is done only once at function-definition time rather than at
each function invocation), but unfortunately the compiler leaves the
uses absolutely untouched and does not compile the resulting defuns.
My suggestion is that the compiler look at the first atom of a
(top-level) list to compile, check whether it is a macro it knows of
and, if so, expands it and recursively compile the result, as it
currently does for more deeply nested forms.  Briefly said, macro
expansion should also occur at top level during compilation.

Many thanks in advance.

Best regards,

					Martin Jourdan