Package: emacs;
Reported by: Aaron Zeng <azeng <at> janestreet.com>
Date: Tue, 8 Apr 2025 22:06:01 UTC
Severity: normal
Found in version 30.1.50
Message #8 received at 77658 <at> debbugs.gnu.org (full text, mbox):
From: Eli Zaretskii <eliz <at> gnu.org> To: Aaron Zeng <azeng <at> janestreet.com>, João Távora <joaotavora <at> gmail.com> Cc: 77658 <at> debbugs.gnu.org, app-emacs-dev <at> janestreet.com Subject: Re: bug#77658: 30.1.50; Eglot: wrong-type-argument: consp, #<marker at 1 in file> when saving Date: Wed, 09 Apr 2025 15:09:40 +0300
> Cc: app-emacs-dev <at> janestreet.com > Date: Tue, 08 Apr 2025 18:05:27 -0400 > From: Aaron Zeng via "Bug reports for GNU Emacs, > the Swiss army knife of text editors" <bug-gnu-emacs <at> gnu.org> > > > When saving an empty buffer, and while `before-save-hook` has some function for reformatting the buffer (such as an auto-styler), the following error is issued: > > ``` > Error running timer: (wrong-type-argument consp #<marker at 1 in jbuild<src>>) > ``` > > This error can also happen synchronously in `basic-save-buffer` rather than in a timer. Adding João. > Reproduction recipe: > > With `~/src/emacs/trunk` checked out to `9663c959c73d6cca0c56f833d80ff1d9e9708b70`: > > 1. emacs -Q -l ~/src/emacs/trunk/lisp/progmodes/eglot.el > 2. Evaluate the following code: > > ```emacs-lisp > (add-hook 'before-save-hook #'no-op-file-formatter) > > (defun no-op-file-formatter () > (let ((temp-file (make-temp-file "no-op-file-formatter"))) > (save-restriction > (widen) > (write-region nil nil temp-file) > (insert-file-contents temp-file nil nil nil 'replace)))) > ``` > > This adds a dummy "reformatter" to `before-save-hook` that is always a no-op. > > 3. `C-x C-f /tmp/z.c` > 4. `M-x eglot` (clangd must be installed, I suppose) > 5. Make some trivial edits, then `C-x h DEL` to erase the buffer contents > 6. `C-x C-s` > > An error is issued as described above. Also, since `eglot--recent-changes` now includes a malformatted entry, random other things break, too, for example just inserting a space character. Backtrace included below. > > I believe what is happening here, is that `insert-file-contents`, when inserting an empty file into an empty buffer (maybe in other situations as well), may cause `before-change-functions` to be run but `after-change-functions` never runs. Therefore, `eglot--before-changes` inserts an element into `eglot--recent-changes` that `eglot--after-changes` never gets a chance to fix up. > > According to the manual (https://www.gnu.org/software/emacs/manual/html_node/elisp/Change-Hooks.html), this is allowed to happen (i.e., `after-change-functions` might not always run after a change). > > Backtrace for eglot--post-self-insert-hook: > ``` > Debugger entered--Lisp error: (wrong-type-argument consp #<marker at 1 in z.c>) > json-serialize((:jsonrpc "2.0" :method "textDocument/didChange" :params (:textDocument (:uri "file:///tmp/z.c" :version 8) :contentChanges [(:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength (1 . #<marker at 1 in z.c>) :text (1 . #<marker (moves after insertion) at 3 in z.c>)) (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text " ") (:range (:start (:line 0 :character 1) :end (:line 0 :character 1)) :rangeLength 0 :text "\n") (:range (:start (:line 0 :character 0) :end (:line 0 :character 1)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 1 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 2 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 1 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n")])) :false-object :json-false :null-object nil) > jsonrpc--json-encode((:jsonrpc "2.0" :method "textDocument/didChange" :params (:textDocument (:uri "file:///tmp/z.c" :version 8) :contentChanges [(:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength (1 . #<marker at 1 in z.c>) :text (1 . #<marker (moves after insertion) at 3 in z.c>)) (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text " ") (:range (:start (:line 0 :character 1) :end (:line 0 :character 1)) :rangeLength 0 :text "\n") (:range (:start (:line 0 :character 0) :end (:line 0 :character 1)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 1 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 2 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 1 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n")]))) > #f(compiled-function (arg1 &rest rest) "Send MESSAGE, a JSON object, to CONNECTION." #<bytecode -0x12f6683e8a694a20>)(#<eglot-lsp-server eglot-lsp-server-3450ea1> :method :textDocument/didChange :params (:textDocument (:uri "file:///tmp/z.c" :version 8) :contentChanges [(:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength (1 . #<marker at 1 in z.c>) :text (1 . #<marker (moves after insertion) at 3 in z.c>)) (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text " ") (:range (:start (:line 0 :character 1) :end (:line 0 :character 1)) :rangeLength 0 :text "\n") (:range (:start (:line 0 :character 0) :end (:line 0 :character 1)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 1 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 2 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 1 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n")])) > apply(#f(compiled-function (arg1 &rest rest) "Send MESSAGE, a JSON object, to CONNECTION." #<bytecode -0x12f6683e8a694a20>) #<eglot-lsp-server eglot-lsp-server-3450ea1> (:method :textDocument/didChange :params (:textDocument (:uri "file:///tmp/z.c" :version 8) :contentChanges [(:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength (1 . #<marker at 1 in z.c>) :text (1 . #<marker (moves after insertion) at 3 in z.c>)) (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text " ") (:range (:start (:line 0 :character 1) :end (:line 0 :character 1)) :rangeLength 0 :text "\n") (:range (:start (:line 0 :character 0) :end (:line 0 :character 1)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 1 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 2 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 1 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n")]))) > jsonrpc-connection-send(#<eglot-lsp-server eglot-lsp-server-3450ea1> :method :textDocument/didChange :params (:textDocument (:uri "file:///tmp/z.c" :version 8) :contentChanges [(:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength (1 . #<marker at 1 in z.c>) :text (1 . #<marker (moves after insertion) at 3 in z.c>)) (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text " ") (:range (:start (:line 0 :character 1) :end (:line 0 :character 1)) :rangeLength 0 :text "\n") (:range (:start (:line 0 :character 0) :end (:line 0 :character 1)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 1 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 2 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 1 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n")])) > jsonrpc-notify(#<eglot-lsp-server eglot-lsp-server-3450ea1> :textDocument/didChange (:textDocument (:uri "file:///tmp/z.c" :version 8) :contentChanges [(:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength (1 . #<marker at 1 in z.c>) :text (1 . #<marker (moves after insertion) at 3 in z.c>)) (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text " ") (:range (:start (:line 0 :character 1) :end (:line 0 :character 1)) :rangeLength 0 :text "\n") (:range (:start (:line 0 :character 0) :end (:line 0 :character 1)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 1 :character 0)) :rangeLength 0 :text "\n") (:range (:start (:line 1 :character 0) :end (:line 2 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 1 :character 0)) :rangeLength 1 :text "") (:range (:start (:line 0 :character 0) :end (:line 0 :character 0)) :rangeLength 0 :text "\n")])) > (let* ((server (eglot--current-server-or-lose)) (sync-capability (eglot-server-capable :textDocumentSync)) (sync-kind (if (numberp sync-capability) sync-capability (plist-get sync-capability :change))) (full-sync-p (or (eq sync-kind 1) (eq :emacs-messup eglot--recent-changes)))) (jsonrpc-notify server :textDocument/didChange (list :textDocument (eglot--VersionedTextDocumentIdentifier) :contentChanges (if full-sync-p (vector (list ':text (save-excursion (save-restriction ... ...)))) (let* ((--cl-var-- (reverse eglot--recent-changes)) (beg nil) (end nil) (len nil) (text nil) (--cl-var--) (--cl-var-- [])) (while (consp --cl-var--) (progn (setq --cl-var-- ...) (setq beg ...) (setq end ...) (setq len ...) (setq text ...)) (setq --cl-var-- (vconcat --cl-var-- ...)) (setq --cl-var-- (cdr --cl-var--))) --cl-var--)))) (setq eglot--recent-changes nil) (jsonrpc--call-deferred server)) > (progn (let* ((server (eglot--current-server-or-lose)) (sync-capability (eglot-server-capable :textDocumentSync)) (sync-kind (if (numberp sync-capability) sync-capability (plist-get sync-capability :change))) (full-sync-p (or (eq sync-kind 1) (eq :emacs-messup eglot--recent-changes)))) (jsonrpc-notify server :textDocument/didChange (list :textDocument (eglot--VersionedTextDocumentIdentifier) :contentChanges (if full-sync-p (vector (list ':text (save-excursion ...))) (let* ((--cl-var-- ...) (beg nil) (end nil) (len nil) (text nil) (--cl-var--) (--cl-var-- ...)) (while (consp --cl-var--) (progn ... ... ... ... ...) (setq --cl-var-- ...) (setq --cl-var-- ...)) --cl-var--)))) (setq eglot--recent-changes nil) (jsonrpc--call-deferred server))) > (if eglot--recent-changes (progn (let* ((server (eglot--current-server-or-lose)) (sync-capability (eglot-server-capable :textDocumentSync)) (sync-kind (if (numberp sync-capability) sync-capability (plist-get sync-capability :change))) (full-sync-p (or (eq sync-kind 1) (eq :emacs-messup eglot--recent-changes)))) (jsonrpc-notify server :textDocument/didChange (list :textDocument (eglot--VersionedTextDocumentIdentifier) :contentChanges (if full-sync-p (vector (list ... ...)) (let* (... ... ... ... ... ... ...) (while ... ... ... ...) --cl-var--)))) (setq eglot--recent-changes nil) (jsonrpc--call-deferred server)))) > eglot--signal-textDocument/didChange() > (if immediate nil (eglot--signal-textDocument/didChange)) > (progn (if immediate nil (eglot--signal-textDocument/didChange)) (jsonrpc-request server method params :timeout timeout :cancel-on-input (cond ((and cancel-on-input eglot-advertise-cancellation) #'(lambda (id) (jsonrpc-notify server '$/cancelRequest (list ... id)))) (cancel-on-input)) :cancel-on-input-retval cancel-on-input-retval)) > (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '(:immediate :timeout :cancel-on-input :cancel-on-input-retval :allow-other-keys)) (if (cdr --cl-keys--) nil (error "Missing argument for %s" (car --cl-keys--))) (setq --cl-keys-- (cdr (cdr --cl-keys--)))) ((car (cdr (memq ... --cl-rest--))) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:immediate :timeout :cancel-on-input :cancel-on-input-retval)" (car --cl-keys--)))))) (progn (if immediate nil (eglot--signal-textDocument/didChange)) (jsonrpc-request server method params :timeout timeout :cancel-on-input (cond ((and cancel-on-input eglot-advertise-cancellation) #'(lambda (id) (jsonrpc-notify server ... ...))) (cancel-on-input)) :cancel-on-input-retval cancel-on-input-retval))) > (let* ((immediate (car (cdr (plist-member --cl-rest-- ':immediate)))) (timeout (car (cdr (plist-member --cl-rest-- ':timeout)))) (cancel-on-input (car (cdr (plist-member --cl-rest-- ':cancel-on-input)))) (cancel-on-input-retval (car (cdr (plist-member --cl-rest-- ':cancel-on-input-retval))))) (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '...) (if (cdr --cl-keys--) nil (error "Missing argument for %s" ...)) (setq --cl-keys-- (cdr ...))) ((car (cdr ...)) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:immediate :timeout :cancel-on-input :cancel-on-input-retval)" (car --cl-keys--)))))) (progn (if immediate nil (eglot--signal-textDocument/didChange)) (jsonrpc-request server method params :timeout timeout :cancel-on-input (cond ((and cancel-on-input eglot-advertise-cancellation) #'(lambda ... ...)) (cancel-on-input)) :cancel-on-input-retval cancel-on-input-retval)))) > eglot--request(#<eglot-lsp-server eglot-lsp-server-3450ea1> :textDocument/onTypeFormatting (:textDocument (:uri "file:///tmp/z.c") :options (:tabSize 8 :insertSpaces :json-false :insertFinalNewline t :trimFinalNewlines t) :position (:line 1 :character 0) :ch "\n")) > (eglot--apply-text-edits (eglot--request (eglot--current-server-or-lose) method (cons :textDocument (cons (eglot--TextDocumentIdentifier) (cons :options (cons (list :tabSize tab-width :insertSpaces (if indent-tabs-mode :json-false t) :insertFinalNewline (if require-final-newline t :json-false) :trimFinalNewlines (if delete-trailing-lines t :json-false)) args))))) nil on-type-format) > (progn (eglot-server-capable-or-lose cap) (eglot--apply-text-edits (eglot--request (eglot--current-server-or-lose) method (cons :textDocument (cons (eglot--TextDocumentIdentifier) (cons :options (cons (list :tabSize tab-width :insertSpaces ... :insertFinalNewline ... :trimFinalNewlines ...) args))))) nil on-type-format)) > (let ((method x37) (cap x39) (args x41)) (progn (eglot-server-capable-or-lose cap) (eglot--apply-text-edits (eglot--request (eglot--current-server-or-lose) method (cons :textDocument (cons (eglot--TextDocumentIdentifier) (cons :options (cons ... args))))) nil on-type-format))) > (progn (ignore (null x42)) (let ((method x37) (cap x39) (args x41)) (progn (eglot-server-capable-or-lose cap) (eglot--apply-text-edits (eglot--request (eglot--current-server-or-lose) method (cons :textDocument (cons (eglot--TextDocumentIdentifier) (cons :options ...)))) nil on-type-format)))) > (let* ((x41 (car-safe x40)) (x42 (cdr-safe x40))) (progn (ignore (null x42)) (let ((method x37) (cap x39) (args x41)) (progn (eglot-server-capable-or-lose cap) (eglot--apply-text-edits (eglot--request (eglot--current-server-or-lose) method (cons :textDocument (cons ... ...))) nil on-type-format))))) > (progn (ignore (consp x40)) (let* ((x41 (car-safe x40)) (x42 (cdr-safe x40))) (progn (ignore (null x42)) (let ((method x37) (cap x39) (args x41)) (progn (eglot-server-capable-or-lose cap) (eglot--apply-text-edits (eglot--request (eglot--current-server-or-lose) method (cons :textDocument ...)) nil on-type-format)))))) > (let* ((x39 (car-safe x38)) (x40 (cdr-safe x38))) (progn (ignore (consp x40)) (let* ((x41 (car-safe x40)) (x42 (cdr-safe x40))) (progn (ignore (null x42)) (let ((method x37) (cap x39) (args x41)) (progn (eglot-server-capable-or-lose cap) (eglot--apply-text-edits (eglot--request ... method ...) nil on-type-format))))))) > (progn (ignore (consp x38)) (let* ((x39 (car-safe x38)) (x40 (cdr-safe x38))) (progn (ignore (consp x40)) (let* ((x41 (car-safe x40)) (x42 (cdr-safe x40))) (progn (ignore (null x42)) (let ((method x37) (cap x39) (args x41)) (progn (eglot-server-capable-or-lose cap) (eglot--apply-text-edits ... nil on-type-format)))))))) > (let* ((x37 (car-safe val)) (x38 (cdr-safe val))) (progn (ignore (consp x38)) (let* ((x39 (car-safe x38)) (x40 (cdr-safe x38))) (progn (ignore (consp x40)) (let* ((x41 (car-safe x40)) (x42 (cdr-safe x40))) (progn (ignore (null x42)) (let (... ... ...) (progn ... ...)))))))) > (progn (ignore (consp val)) (let* ((x37 (car-safe val)) (x38 (cdr-safe val))) (progn (ignore (consp x38)) (let* ((x39 (car-safe x38)) (x40 (cdr-safe x38))) (progn (ignore (consp x40)) (let* ((x41 ...) (x42 ...)) (progn (ignore ...) (let ... ...)))))))) > (let* ((val (cond ((and beg on-type-format) (list ':textDocument/onTypeFormatting ':documentOnTypeFormattingProvider (list ':position (eglot--pos-to-lsp-position beg) ':ch (string on-type-format)))) ((and beg end) (list ':textDocument/rangeFormatting ':documentRangeFormattingProvider (list ':range (list :start ... :end ...)))) (t '(:textDocument/formatting :documentFormattingProvider nil))))) (progn (ignore (consp val)) (let* ((x37 (car-safe val)) (x38 (cdr-safe val))) (progn (ignore (consp x38)) (let* ((x39 (car-safe x38)) (x40 (cdr-safe x38))) (progn (ignore (consp x40)) (let* (... ...) (progn ... ...)))))))) > eglot-format(2 nil 10) > (progn (eglot-format (point) nil eglot--last-inserted-char)) > (if (and ot-provider (condition-case nil (progn (or (eq eglot--last-inserted-char (seq-first (plist-get ot-provider :firstTriggerCharacter))) (cl-find eglot--last-inserted-char (plist-get ot-provider :moreTriggerCharacter) :key #'seq-first))) (error nil))) (progn (eglot-format (point) nil eglot--last-inserted-char))) > (let ((ot-provider (eglot-server-capable :documentOnTypeFormattingProvider))) (if (and ot-provider (condition-case nil (progn (or (eq eglot--last-inserted-char (seq-first ...)) (cl-find eglot--last-inserted-char (plist-get ot-provider :moreTriggerCharacter) :key #'seq-first))) (error nil))) (progn (eglot-format (point) nil eglot--last-inserted-char)))) > eglot--post-self-insert-hook() > newline(nil 1) > funcall-interactively(newline nil 1) > command-execute(newline) > ``` > > > In GNU Emacs 30.1.50 (build 2, x86_64-pc-linux-gnu, X toolkit, cairo > version 1.15.12, Xaw scroll bars) of 2025-03-28 built on > igm-qws-u22796a > Repository revision: 3e6424e1f1816332e574035bc73143551a69efb6 > Windowing system distributor 'The X.Org Foundation', version 11.0.12011000 > System Description: Rocky Linux 8.10 (Green Obsidian) > > Configured using: > 'configure --config-cache --with-x-toolkit=lucid --without-gpm > --without-gconf --without-selinux --without-imagemagick --with-modules > --with-gif=no --with-cairo --with-rsvg --without-compress-install > --with-tree-sitter --with-native-compilation=aot > --prefix=/usr/local/home/garnish/raw-emacs/30-20250328_130425' > > Configured features: > CAIRO DBUS FREETYPE GLIB GMP GNUTLS GSETTINGS HARFBUZZ JPEG LIBSYSTEMD > LIBXML2 MODULES NATIVE_COMP NOTIFY INOTIFY PDUMPER PNG RSVG SECCOMP > SOUND SQLITE3 THREADS TIFF TOOLKIT_SCROLL_BARS TREE_SITTER X11 XDBE XIM > XINPUT2 XPM LUCID ZLIB > > Important settings: > value of $LANG: en_US.utf8 > locale-coding-system: utf-8-unix > > Major mode: Fundamental
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.