rust-lang / rust-mode

Emacs configuration for Rust
Apache License 2.0
1.12k stars 179 forks source link

crippy bug: stdin buffer if "Rust Format On Save" is on #357

Closed Dushistov closed 4 years ago

Dushistov commented 4 years ago

It is random, I have no idea how to reproduce it.

Time to time when I save current buffer with rust-mode, let's call it a.rs, emacs does not save it, and instead it open buffer stdin, in other words I press C-x s and current buffer change to "stdin" buffer. This buffer is empty, and a.rs still in modified state. I switch back to a.rs, try to save it and again empty stdin instead of a.rs.

So actually I can not save my rust buffer, I have to set "Rust Format On Save" to off to make possible save, or change mode from rust-mode to text-mode.

This bug appear only recently, so I suppose it is related to last "rustfmt" changes in master.

GNU Emacs 26.3 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.10) of 2019-08-29 rust-mode 20200229.1547

mookid commented 4 years ago

I will have a look. What version of the repo do you have installed?

Dushistov commented 4 years ago

@mookid

What version of the repo do you have installed?

I installed rust-mode via melpa and it is as I mentioned:

rust-mode 20200229.1547

I cloned current master (86650056717be0661feae32e294ff991c087ed4d) and seems it is completely match current master:

LANG=C diff -ur   ~/.emacs.d/elpa/rust-mode-20200229.1547/ rust-mode/
Only in rust-mode/: .git
Only in rust-mode/: .github
Only in rust-mode/: .gitignore
Only in rust-mode/: .travis.yml
Only in rust-mode/: LICENSE-APACHE
Only in rust-mode/: LICENSE-MIT
Only in rust-mode/: README.md
Only in rust-mode/: run_rust_emacs_tests.sh
Only in rust-mode/: run_rust_emacs_tests_docker.sh
Only in /home/evgeniy/.emacs.d/elpa/rust-mode-20200229.1547/: rust-mode-autoloads.el
Only in /home/evgeniy/.emacs.d/elpa/rust-mode-20200229.1547/: rust-mode-pkg.el
Only in rust-mode/: rust-mode-tests.el
diff -ur /home/evgeniy/.emacs.d/elpa/rust-mode-20200229.1547/rust-mode.el rust-mode/rust-mode.el
--- /home/evgeniy/.emacs.d/elpa/rust-mode-20200229.1547/rust-mode.el    2020-03-01 20:36:21.753564920 +0300
+++ rust-mode/rust-mode.el      2020-03-02 19:53:22.723344491 +0300
@@ -1,7 +1,6 @@
 ;;; rust-mode.el --- A major emacs mode for editing Rust source code -*-lexical-binding: t-*-

 ;; Version: 0.5.0
-;; Package-Version: 20200229.1547
 ;; Author: Mozilla
 ;; Url: https://github.com/rust-lang/rust-mode
 ;; Keywords: languages
Only in /home/evgeniy/.emacs.d/elpa/rust-mode-20200229.1547/: rust-mode.elc
Only in rust-mode/: test-by-cp
Only in rust-mode/: test-from-git
Only in rust-mode/: test-project
mookid commented 4 years ago

@Dushistov I fail to reproduce it for now. Can you please install the last version of master and try again?

Also, can I see your config somewhere?

Dushistov commented 4 years ago

@mookid

Can you please install the last version of master and try again?

I installed it. And I will wait for bug.

Also, can I see your config somewhere?


;; Rust
(require 'req-package)
(use-package rust-mode
:mode "\\.rs\\'"
:init
(setq rust-format-on-save t)
(setq-default indent-tabs-mode nil))

(require 'rust-mode) (require 'lsp-mode) (add-hook 'rust-mode-hook #'lsp)

(require 'cargo) (setq cargo-process--command-check "check --release --all-targets") (setq cargo-process--command-test "test --release") (setq cargo-process--command-current-test "test --release")

(defun cargo-cmds-release-toggle () (interactive) (if (equal cargo-process--command-check "check --release --all-targets") (progn (setq cargo-process--command-check "check --all-targets") (setq cargo-process--command-test "test") (setq cargo-process--command-current-test "test")) (progn (setq cargo-process--command-check "check --release --all-targets") (setq cargo-process--command-test "test --release") (setq cargo-process--command-current-test "test --release"))))

(add-hook 'rust-mode-hook (lambda () (local-set-key "\C-cj" 'lsp-find-definition) (local-set-key "\M-*" 'pop-tag-mark) (local-set-key (kbd "C-") 'cargo-process-check) (local-set-key (kbd "C-") 'cargo-process-check) (local-set-key (kbd "C-") 'cargo-process-test) (local-set-key (kbd "C-") 'cargo-process-test) (local-set-key (kbd "C-") 'recompile) (local-set-key (kbd "C-") 'recompile) (local-set-key (kbd "C-") 'cargo-process-current-test) (column-enforce-mode) ))

(defun rust-multiline-error-filter () (save-excursion (let ((start compilation-filter-start) (end (point))) (goto-char start) (beginning-of-line) ; is this necessary? should not harm ... (while (re-search-forward "^\(error\|warning\)\(?:\[E[0-9]+\]\)?:[^\n]*[\n]" end t) (put-text-property (match-beginning 0) (match-end 0) 'compilation-multiline t))))) (add-hook 'rust-mode-hook (lambda () (add-hook 'compilation-filter-hook #'rust-multiline-error-filter)))

;; Rust end

jamii commented 4 years ago

I have the same issue, and it seems to happen exactly when the file has a syntax error. Valid code will be formatted fine, but invalid code results in opening stdin.

mookid commented 4 years ago

What kind of syntax error are we talking about? An example would be super useful.

I tried for instance fn func( or "abcd and my file is saved normally.

jamii commented 4 years ago

I tried

fn main() {

    }

It saved fine and reformatted to fn main() {}.

But

fn main() {

opens stdin.

This happens for every syntax error I can think of, so likely there is some other difference between our setups.

jamii commented 4 years ago

In *Messages* I see:

rust--format-error-handler: End of buffer
jamii commented 4 years ago

I have rust-mode 20200303.932 from melpa and emacs 26.3.

mookid commented 4 years ago

Thanks for the repro! I still can't trigger it myself. What is (list rust-format-show-buffer rust-format-goto-problem)?

What if you redefine rust--format-error-handler like so?

(defun rust--format-error-handler ()
  (condition-case nil
      (let ((ok nil))
        (when rust-format-show-buffer
          (display-buffer (get-buffer rust-rustfmt-buffername))
          (setq ok t))
        (when rust-format-goto-problem
          (rust-goto-format-problem)
          (setq ok t))
        ok)))
jamii commented 4 years ago

What is (list rust-format-show-buffer rust-format-goto-problem)?

(nil t)

What if you redefine rust--format-error-handler like so?

Same behaviour.

jamii commented 4 years ago

Also maybe helpful

[nix-shell:~/materialize]$ rustfmt --version
rustfmt 1.4.11-stable (1838235 2019-12-03)
mookid commented 4 years ago

Ok, this is different from what I have. And what is the content of *rustfmt*?

edit: fixed name.

jamii commented 4 years ago

*rust-fmt* doesn't seem to exist.

jamii commented 4 years ago

I have to head out now, but I'll see tomorrow if I can make a repro with nix.

mookid commented 4 years ago

Closing for now, please repopen with other details if it reappears.

jamii commented 4 years ago

reappears

I just updated rust-mode to 20200322.1749 and I still consistently have this behavior on every save with invalid syntax.

mookid commented 4 years ago

@jamii can you please add more details? what is the content of the rustfmt buffer?

jamii commented 4 years ago

*rustfmt* contains

error: this file contains an un-closed delimiter
 --> <stdin>:1:13
  |
1 | fn main() {
  |           - ^
  |           |
  |           un-closed delimiter

*Messages* contains

Saving file /home/jamie/test.rs...
Quit

(The quit is me exiting the "save file" dialog)

Otherwise the behaviour I see is the same as before - when I hit save it switches to a buffer called <stdin> and then asks where to save that buffer.

jamii commented 4 years ago

So it gets to

Debugger entered--returning value: 5
  1-(6)
* (forward-char (1- (cdr target-point)))
* (progn (switch-to-buffer target-buffer) (goto-char (point-min)) (forward-line (1- (car target-point))) (forward-char (1- (cdr target-point))))
* (if (and target-buffer target-point) (progn (switch-to-buffer target-buffer) (goto-char (point-min)) (forward-line (1- (car target-point))) (forward-char (1- (cdr target-point)))))
* (let ((target-buffer (save-current-buffer (set-buffer rustfmt) (save-excursion (goto-char (point-min)) (if (re-search-forward "--> \\([^:]+\\):" nil t) (progn (match-string 1)))))) (target-point (save-current-buffer (set-buffer rustfmt) (let ((regex "--> [^:]+:\\([0-9]+\\):\\([0-9]+\\)")) (if (or (re-search-forward regex nil t) (progn ... ...)) (progn (cons ... ...)))))) (target-problem (save-current-buffer (set-buffer rustfmt) (save-excursion (if (re-search-backward "^error:.+\n" nil t) (progn (forward-char ...) (let ... ...))))))) (if (and target-buffer target-point) (progn (switch-to-buffer target-buffer) (goto-char (point-min)) (forward-line (1- (car target-point))) (forward-char (1- (cdr target-point))))) (message target-problem))
* (if (not rustfmt) (message "No *rustfmt*, no problems.") (let ((target-buffer (save-current-buffer (set-buffer rustfmt) (save-excursion (goto-char (point-min)) (if (re-search-forward "--> \\([^:]+\\):" nil t) (progn ...))))) (target-point (save-current-buffer (set-buffer rustfmt) (let ((regex "--> [^:]+:\\([0-9]+\\):\\([0-9]+\\)")) (if (or ... ...) (progn ...))))) (target-problem (save-current-buffer (set-buffer rustfmt) (save-excursion (if (re-search-backward "^error:.+\n" nil t) (progn ... ...)))))) (if (and target-buffer target-point) (progn (switch-to-buffer target-buffer) (goto-char (point-min)) (forward-line (1- (car target-point))) (forward-char (1- (cdr target-point))))) (message target-problem)))
* (let ((rustfmt (get-buffer rust-rustfmt-buffername))) (if (not rustfmt) (message "No *rustfmt*, no problems.") (let ((target-buffer (save-current-buffer (set-buffer rustfmt) (save-excursion (goto-char ...) (if ... ...)))) (target-point (save-current-buffer (set-buffer rustfmt) (let (...) (if ... ...)))) (target-problem (save-current-buffer (set-buffer rustfmt) (save-excursion (if ... ...))))) (if (and target-buffer target-point) (progn (switch-to-buffer target-buffer) (goto-char (point-min)) (forward-line (1- (car target-point))) (forward-char (1- (cdr target-point))))) (message target-problem))))
* (lambda nil "Jumps to problem reported by rustfmt, if any.\nIn case of multiple problems cycles through them. Displays the\nrustfmt complain in the echo area." (interactive) (let ((rustfmt (get-buffer rust-rustfmt-buffername))) (if (not rustfmt) (message "No *rustfmt*, no problems.") (let ((target-buffer (save-current-buffer (set-buffer rustfmt) (save-excursion ... ...))) (target-point (save-current-buffer (set-buffer rustfmt) (let ... ...))) (target-problem (save-current-buffer (set-buffer rustfmt) (save-excursion ...)))) (if (and target-buffer target-point) (progn (switch-to-buffer target-buffer) (goto-char (point-min)) (forward-line (1- ...)) (forward-char (1- ...)))) (message target-problem)))))()
* apply((lambda nil "Jumps to problem reported by rustfmt, if any.\nIn case of multiple problems cycles through them. Displays the\nrustfmt complain in the echo area." (interactive) (let ((rustfmt (get-buffer rust-rustfmt-buffername))) (if (not rustfmt) (message "No *rustfmt*, no problems.") (let ((target-buffer (save-current-buffer ... ...)) (target-point (save-current-buffer ... ...)) (target-problem (save-current-buffer ... ...))) (if (and target-buffer target-point) (progn (switch-to-buffer target-buffer) (goto-char ...) (forward-line ...) (forward-char ...))) (message target-problem))))) nil)
* rust-goto-format-problem()
  rust--format-error-handler()
  rust-format-buffer()
  rust-before-save-hook()
  run-hooks(before-save-hook)
  basic-save-buffer(t)
  save-buffer(1)
  funcall-interactively(save-buffer 1)
  call-interactively(save-buffer nil nil)
  command-execute(save-buffer)

At this point target-buffer is set to "<stdin>" and that buffer is open. If I hit next it jumps to:

Debugger entered--entering a function:
* format("rust-before-save-hook: %S %S" end-of-buffer nil)
  rust-before-save-hook()
  run-hooks(before-save-hook)
  basic-save-buffer(t)
  save-buffer(1)
  funcall-interactively(save-buffer 1)
  call-interactively(save-buffer nil nil)
  command-execute(save-buffer)

It doesn't ever reach (message target-problem) on L1541 of rust-mode.el, so I assume this jump was some kind of error handler? I'm not really sure how to follow the jump in the debugger.

jamii commented 4 years ago

At this point the <stdin> buffer is empty, so that forward-char will result in an "end of buffer" error.

jamii commented 4 years ago

The numbers in target-point seem to be correct for the syntax error in test.rs, so perhaps target-buffer is just wrong.

jamii commented 4 years ago

Oh I see why you're asking about *rustfmt* now - it does have the wrong filename.

I put a breakpoint on rust--format-fix-rustfmt-buffer and it doesn't seem to get triggered.

mookid commented 4 years ago

@jamii can you try https://github.com/rust-lang/rust-mode/commit/e4bdc1defc8b215835d5e7de5c7f2b4137187455 ?

I believe that for some reason rust--format-fix-rustfmt-buffer fails to replace the stdin markers, it might be because because of some ansi-color thing (it might explain why it's different across machines as well). The content of rustfmt is supposed to be

error: this file contains an un-closed delimiter
 --> foo.rs:1:13
  |
1 | fn main() {
  |           - ^
  |           |
  |           un-closed delimiter

after rust--format-fix-rustfmt-buffer.

mookid commented 4 years ago

Oh I see why you're asking about *rustfmt* now - it does have the wrong filename.

I put a breakpoint on rust--format-fix-rustfmt-buffer and it doesn't seem to get triggered.

interesting! and what if ret in rust--format-call?

jamii commented 4 years ago

ret is 1.

It seems to skip straight past the call to fix, so I'm suspicious that I have old cached bytecode or something. If I evaluate the source for rust--format-call I stop seeing this bug until I restart.

mookid commented 4 years ago

Cool. So I guess it can be closed?

jamii commented 4 years ago

Yeah, force-recompiling bytecode fixes it. Sorry for wasting your time :S

mookid commented 4 years ago

no worries.