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
View this message in rfc822 format
From: Eli Zaretskii <eliz <at> gnu.org> To: azeng <at> janestreet.com, joaotavora <at> gmail.com Cc: 77658 <at> debbugs.gnu.org, app-emacs-dev <at> janestreet.com Subject: bug#77658: 30.1.50; Eglot: wrong-type-argument: consp, #<marker at 1 in file> when saving Date: Sat, 26 Apr 2025 14:08:37 +0300
Ping! João, any suggestions or comments? > Cc: 77658 <at> debbugs.gnu.org, app-emacs-dev <at> janestreet.com > Date: Wed, 09 Apr 2025 15:09:40 +0300 > From: Eli Zaretskii <eliz <at> gnu.org> > > > 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.