Subject: 24.3.50; regression in haskell-mode caused by emacs commit 116129

From: Ian Kelling <[email-masked]>
Date: Tue, 18 Mar 2014 05:44:04 -0700

The bug causes an error on execution of haskell-interactive-switch, and other
haskell-interactive commands, leaving haskell-interactive-mode totally broken.

The backtrace I have is a bit confusing because the code listed is a result of
macro expansion, but here is an explanation of what appears to be happening.

This line from haskell-mode's haskell-process.el is executed
(check-type cmd haskell-command)
which macro expands to become
(haskell-command cmd)
but haskell-command is a defstruct, leading to void-function error.

The expansion problem is from cl--make-type-test, specifically last line of code
added from the bzr 116129 commit. If not that line, I have tested for sure that
the problem was introduced by that commit.

It manifests only when haskell-mode is not compiled, or haskell-mode's elisp files
are each compiled with separate emacs commands.

Excerpt from that commit including its last line:

-              (if (fboundp namep) (list namep val)
-                (list (intern (concat name "-p")) val)))))
+              (cond
+                ((cl--macroexp-fboundp namep) (list namep val))
+                ((cl--macroexp-fboundp
+                  (setq namep (intern (concat name "-p"))))
+                 (list namep val))
+                (t (list type val))))))

haskell-mode is not part of emacs, but I wasn't able to easily reproduce it with
test code. I tested this with emacs -Q, and then loading a recent version of
haskell-mode.

Here is a backtrace output:

Debugger entered--Lisp error: (void-function haskell-command)
  (haskell-command cmd)
  (or (haskell-command cmd) (signal (quote wrong-type-argument) (list (quote
haskell-command) cmd (quote cmd))))
  (progn (or (haskell-command cmd) (signal (quote wrong-type-argument) (list
(quote haskell-command) cmd (quote cmd)))) nil)
  haskell-process-cmd-queue-add(((name . "tmphaskproj") (inferior-process .
#<process tmphaskproj>) (is-restarting) (current-command) (session (name .
"tmphaskproj") (current-dir . "/tmp/") (cabal-checksum) (cabal-dir . "/tmp/")
(process (name . "tmphaskproj") (inferior-process . #<process tmphaskproj>)
(is-restarting) (current-command) #1))) [cl-struct-haskell-command ((name .
"tmphaskproj") (inferior-process . #<process tmphaskproj>) (is-restarting)
(current-command) (session (name . "tmphaskproj") (current-dir . "/tmp/")
(cabal-checksum) (cabal-dir . "/tmp/") (process (name . "tmphaskproj")
(inferior-process . #<process tmphaskproj>) (is-restarting) (current-command)
#2))) (lambda (process) (haskell-process-send-string process ":set prompt \"> \"")
(haskell-process-send-string process "Prelude.putStrLn \"\"")
(haskell-process-send-string process ":set -v1")) (lambda (process buffer) (if
(haskell-process-consume process "^*** WARNING: \\(.+\\) is writable by someone
else, IGNORING!$") (progn (let ((path (match-string 1 buffer)))
(haskell-session-modify (haskell-process-session process) (quote ignored-files)
(function (lambda ... ...))) (haskell-interactive-mode-compile-warning
(haskell-process-session process) (format "GHCi is ignoring: %s (run M-x
haskell-process-unignore)" path)))))) (lambda (process _)
(haskell-interactive-mode-echo (haskell-process-session process) (concat (nth
(random (length haskell-process-greetings)) haskell-process-greetings) (if
haskell-process-show-debug-tips (progn "\nIf I break, you can:\n  1. Restart:
      M-x haskell-process-restart\n  2. Configure logging: C-h v
haskell-process-log (useful for debugging)\n  3. General config:    M-x
customize-mode\n  4. Hide these tips:   C-h v haskell-process-show-debug-tips")))))])
  haskell-process-queue-command(((name . "tmphaskproj") (inferior-process .
#<process tmphaskproj>) (is-restarting) (current-command) (session (name .
"tmphaskproj") (current-dir . "/tmp/") (cabal-checksum) (cabal-dir . "/tmp/")
(process (name . "tmphaskproj") (inferior-process . #<process tmphaskproj>)
(is-restarting) (current-command) #1))) [cl-struct-haskell-command ((name .
"tmphaskproj") (inferior-process . #<process tmphaskproj>) (is-restarting)
(current-command) (session (name . "tmphaskproj") (current-dir . "/tmp/")
(cabal-checksum) (cabal-dir . "/tmp/") (process (name . "tmphaskproj")
(inferior-process . #<process tmphaskproj>) (is-restarting) (current-command)
#2))) (lambda (process) (haskell-process-send-string process ":set prompt \"> \"")
(haskell-process-send-string process "Prelude.putStrLn \"\"")
(haskell-process-send-string process ":set -v1")) (lambda (process buffer) (if
(haskell-process-consume process "^*** WARNING: \\(.+\\) is writable by someone
else, IGNORING!$") (progn (let ((path (match-string 1 buffer)))
(haskell-session-modify (haskell-process-session process) (quote ignored-files)
(function (lambda ... ...))) (haskell-interactive-mode-compile-warning
(haskell-process-session process) (format "GHCi is ignoring: %s (run M-x
haskell-process-unignore)" path)))))) (lambda (process _)
(haskell-interactive-mode-echo (haskell-process-session process) (concat (nth
(random (length haskell-process-greetings)) haskell-process-greetings) (if
haskell-process-show-debug-tips (progn "\nIf I break, you can:\n  1. Restart:
      M-x haskell-process-restart\n  2. Configure logging: C-h v
haskell-process-log (useful for debugging)\n  3. General config:    M-x
customize-mode\n  4. Hide these tips:   C-h v haskell-process-show-debug-tips")))))])
  haskell-process-send-startup(((name . "tmphaskproj") (inferior-process .
#<process tmphaskproj>) (is-restarting) (current-command) (session (name .
"tmphaskproj") (current-dir . "/tmp/") (cabal-checksum) (cabal-dir . "/tmp/")
(process (name . "tmphaskproj") (inferior-process . #<process tmphaskproj>)
(is-restarting) (current-command) #1))))
  (let ((process (or (haskell-session-process session) (haskell-process-make
(haskell-session-name session)))) (old-queue (haskell-process-get
(haskell-session-process session) (quote command-queue))))
(haskell-session-set-process session process) (haskell-process-set-session process
session) (haskell-process-set-cmd process nil) (haskell-process-set
(haskell-session-process session) (quote is-restarting) nil) (let
((default-directory (haskell-session-cabal-dir session))) (haskell-session-pwd
session) (haskell-process-set-process process (cond ((memql haskell-process-type
(quote (quote ghci))) (haskell-process-log (format "Starting inferior GHCi process
%s ..." haskell-process-path-ghci)) (apply (function start-process) (append (list
... nil haskell-process-path-ghci) haskell-process-args-ghci))) ((memql
haskell-process-type (quote (quote cabal-repl))) (haskell-process-log (format
"Starting inferior `cabal repl' process using %s ..." haskell-process-path-cabal))
(apply (function start-process) (append (list ... nil haskell-process-path-cabal)
(quote ...) haskell-process-args-cabal-repl (let ... ...)))) ((memql
haskell-process-type (quote (quote cabal-ghci))) (haskell-process-log (format
"Starting inferior cabal-ghci process using %s ..."
haskell-process-path-cabal-ghci)) (start-process (haskell-session-name session)
nil haskell-process-path-cabal-ghci)) ((memql haskell-process-type (quote (quote
cabal-dev))) (let ((dir ...)) (haskell-process-log (format "Starting inferior
cabal-dev process %s -s %s ..." haskell-process-path-cabal-dev dir))
(start-process (haskell-session-name session) nil haskell-process-path-cabal-dev
"ghci" "-s" dir))) ((error "cl-ecase failed: %s, %s" haskell-process-type (quote
(ghci quote cabal-repl quote cabal-ghci quote cabal-dev quote))) nil)))) (progn
(set-process-sentinel (haskell-process-process process) (quote
haskell-process-sentinel)) (set-process-filter (haskell-process-process process)
(quote haskell-process-filter))) (haskell-process-send-startup process) (if (eq
(quote cabal-repl) haskell-process-type) nil (haskell-process-change-dir session
process (haskell-session-current-dir session))) (haskell-process-set process
(quote command-queue) (append (haskell-process-get (haskell-session-process
session) (quote command-queue)) old-queue)) process)
  haskell-process-start(((name . "tmphaskproj") (current-dir . "/tmp/")
(cabal-checksum) (cabal-dir . "/tmp/") (process (name . "tmphaskproj")
(inferior-process . #<process tmphaskproj>) (is-restarting) (current-command)
(session (name . "tmphaskproj") (current-dir . "/tmp/") (cabal-checksum)
(cabal-dir . "/tmp/") #1))))
  (let ((session (set (make-local-variable (quote haskell-session)) (list (cons
(quote name) name))))) (add-to-list (quote haskell-sessions) session)
(haskell-process-start session) session)
  haskell-session-make("tmphaskproj")
  (progn (haskell-session-make name))
  (if (not (string= name "")) (progn (haskell-session-make name)))
  (let ((name (read-from-minibuffer "Project name: "
(haskell-session-default-name)))) (if (not (string= name "")) (progn
(haskell-session-make name))))
  haskell-session-new()
  (or (haskell-session-from-buffer) (haskell-session-new-assume-from-cabal)
(haskell-session-choose) (haskell-session-new))
  (haskell-session-assign (or (haskell-session-from-buffer)
(haskell-session-new-assume-from-cabal) (haskell-session-choose)
(haskell-session-new)))
  (or (haskell-session-maybe) (haskell-session-assign (or
(haskell-session-from-buffer) (haskell-session-new-assume-from-cabal)
(haskell-session-choose) (haskell-session-new))))
  haskell-session()
  (haskell-session-interactive-buffer (haskell-session))
  (let ((buffer (haskell-session-interactive-buffer (haskell-session)))) (if (eq
buffer (window-buffer)) nil (switch-to-buffer-other-window buffer)))
  haskell-interactive-switch()
  call-interactively(haskell-interactive-switch record nil)
  command-execute(haskell-interactive-switch record)
  execute-extended-command(nil "haskell-interactive-switch")
  call-interactively(execute-extended-command nil nil)
  command-execute(execute-extended-command)