-
-
Notifications
You must be signed in to change notification settings - Fork 647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[eval] Refactor exception handling logic and overlay display #3789
Conversation
31b0b69
to
dd7f8c0
Compare
We also have to consider how things will look when the overlays are disabled (as they are configurable, and I also don't think they work in terminal emulators). I'm guessing the errors are just dumped to the minibuffer or the REPL in this case, but I haven't used this in a while and my memory on the subject is fuzzy. |
True that. I think that some error handling was the first thing I did on
I wouldn't worry about this too much. Some of the configuration options in Your suggestions seem reasonable to me at a glance, but I'll need a bit of time to go over the code and remember how things were supposed to behave. |
dd7f8c0
to
1d102d7
Compare
Sounds good! Definitely a lot to unpack here. I'll be using the changes locally meanwhile. |
This might also be a good opportunity to review and update the documentation here https://docs.cider.mx/cider/usage/dealing_with_errors.html to reflect the actual behavior of things. I think it's mostly accurate, but I wouldn't be surprised if something was never documented or has changed. |
This looks great, thank you so much for tackling this 💯 I've actually gone down this exact path more than once of attempting to untangle the mess of error handlers and display logic, and might even have contributed in the past (sorry!) by hacking local workarounds onto the overlay-displaying functions. I pulled these changes locally and nothing seems to be broken so far, will provide feedback if I encounter any issues :) |
@yuhan0 Thanks a lot! Dogfooding this PR before merging is a tremendous help. |
This may also be an opportunity to merge the two variables
Some additional nuance may be needed here – I might be misremembering things, but sometime in the past the Something in the recent-ish batch of changes to compilation error handling seems to have changed this behavior, increasing the friction of eg. inspecting macroexpansion-time errors. (Where most of the time an error overlay will suffice, but occasionally one needs a full stacktrace to track down the source of a failing macro.) |
I have several reservations about this.
It appears that way in the docs, I have to check in the code. I'm fine with that – I don't think that
In that case, I'll look into fixing this. It makes sense to always send the error to |
Yeah, on further reflection I agree that a fully specified map would be too fine-grained to be meaningful, in practice I was thinking it would make it easier to configure a default fallthrough behavior (rather than having to understand the strange negative-enumeration semantics of the current var) Thanks for pointing out that the docs explicitly specify a background-generated error buffer, I should've reported it sooner but assumed that I had been relying on undocumented behavior when it broke. |
cider-eval.el
Outdated
(not (cider-connection-has-capability-p 'jvm-compilation-errors))) | ||
(let ((err-message (apply #'concat | ||
(mapcar (lambda (cause) (nrepl-dict-get cause "message")) | ||
causes)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's the issue, we probably want to use newlines to join the messages of nested exceptions, and do something about the redundant messages in the case of compile-time errors
#{1 1} ;; boom
(->> *e
(clojure.datafy/datafy)
(:via)
(map :message))
;; => (nil
;; "java.lang.IllegalArgumentException: Duplicate key: 1"
;; "Duplicate key: 1")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional note - this redundancy only seems to happen for the :read-source
phase, the other phases don't have this issue. Also the duplicated message doesn't occur when simply relaying the contents of stderr, see
#3338 (comment) for how things used to look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the link to this comment, this is a very helpful to have a list of all cases we need to handle.
Looks like we have a problem with spec errors. This is what I get right now:
Where the ideal output would be this:
So, this "spec explain" output only goes to stderr, not to messages.
Perhaps, it is something that we could handle on the orchard.stacktrace
side (previously, Haystack).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to err on the side of verbosity and let the user decide what is too long - after all the reverse is impossible. There's already a defcustom cider-inline-error-message-function
which can eg. limit the maximum number of lines displayed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This complicates things. I'll see what I can do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I believe it was always generated, but not always displayed (depending on the user settings). Don't recall what was the reasoning for this back in the day. Likely some people were just annoyed to always see the buffer pop, but still wanted to have access to it. One common complaint I got (that ultimately lead to the Monroe fork) was that CIDER was relying too much on special buffers and some people found this annoying and preferred for things like errors to be displayed only in the REPL. (even if caused by some in-line evaluation)
Yeah, I don't think it's important to keep the old behavior. I was just sharing some historical context. |
Just to provide a concrete example: ;; throw an error which pops a buffer
(/ 1 0)
;; and then one that doesn't (phase :macro-syntax-check)
(cond 1) User brings up the error buffer manually ( Expected (old behavior, documented)
Actual (recent regression)
|
Yes, I double-checked, "using overlays" really means "using either overlays or minibuffer or both, depending on |
1d102d7
to
2d778ad
Compare
Update: I've addressed all comments, I think. Triage message is now displayed inline: Compilation errors are now always rendered in @yuhan0 Could you please check it? Note that this PR now also updates cider-nrepl version, you need the latest for everything to work correctly. |
2d778ad
to
4b42612
Compare
@bbatsov I've looked through the doc on errors and it is fine. There is a section on inspecting printed stacktraces that I should remove, I'll do that in a separate PR together with deleting corresponding functions. |
4b42612
to
55e4a26
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall the PR looks in a good shape to me.
5319289
to
a03333e
Compare
@bbatsov Another update – I fixed the issue that the overlay was now displayed when sending code to REPL buffer, e.g.: |
cider-eval.el
Outdated
(not (cider-connection-has-capability-p 'jvm-compilation-errors))) | ||
;; the lack of info with a overlay error. Verify that the provided buffer is | ||
;; visiting a source file. | ||
(when (and code-buffer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I usually named those source-buffer
in other parts of the code.
;; visiting a source file. | ||
(when (and code-buffer | ||
(with-current-buffer code-buffer | ||
(or (cider-clojure-major-mode-p) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those derived-mode-p
checks will also work for the cider-scratch
buffers. Checking the major mode by itself is not enough to determine if a buffer is visiting a file. (buffer-file-name
is a useful thing to check in such cases)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it is helpful in a cider-scratch
buffer though 🤔.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably more that I want to restrict the overlay from appearing in REPL buffer and in ancilary buffers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just mentioned this, because of your comment above about visiting a source file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the alternative is to check if a buffer is not derived from comint-mode
or special-mode
. I'm guessing that comint-mode
might be deriving from special-mode
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither comint-mode
nor special-mode
returns t for the REPL buffer. Guess I'll leave the current check as is, and say in the comment what we want to achieve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad! I forgot cider-repl-mode
doesn't inherit from comint-mode
. This is for historical reasons (as SLIME's REPL was also standalone and early on we modeled almost everything after SLIME). At some point I considered changing it, but I never had much incentive to do this. But overall it doesn't matter much which approach do you chose.
Thanks for checking! Yeah, with bounds it looked slightly cleaner, but I think the simplicity of the current solution beats it. Maybe somebody in the future comes to this again and tries to unify it once more... and it will be easier if we simplify things now. |
374de32
to
7b3cb05
Compare
Let's go! |
Alright, this is a big one. I'll try to give the problem statement first.
The context
We want to show error messages when the user evaluates something wrong. There are two types of errors – runtime and compile-time.
*cider-error*
buffer.The problem (dragons ahead)
Compilation errors with source locations come to us via stderr output, so we have to catch them there and parse the messages. Remember, warnings are also there – and warnings don't produce the actual error. So we definitely have to look into stderr.
Nrepl error handling for
eval
op is a mess. When error happens during evalution, nrepl sendserr
responses (which correspond to stderr printouts), and then sends aneval-error
response – which contains barely any actual exception description. So on the client we have to follow that up withanalyze-last-exception
request. Then cider-nrepl will respond with multiple responses of analyzed exception, one per exception cause.Current CIDER implementation messed something up, so even two
analyze-last-exception
are sent. The first one is sent by thestderr
handler to figure out if the error which happened is a compile-time error to know whether to render the overlay. This is incorrect because stderr output could happen not only because of an error (could be a warning or arbitrary user stderr printing). The secondanalyze...
request is correctly sent insideeval-error
handler. But that one only deals with popping up*cider-error*
buffer.The solution
It took me a real real while to untangle this mess. Here are the exact steps I've taken:
cider--handle-stacktrace-response
. Now this function deals both with*cider-error*
buffer and overlays. It accepts the analyzed exception, so it knows whether the error is a compile-time one.analyze-last-exception
first and then call the supplied callback.cider-default-err-op-handler
,cider-default-err-handler
,nrepl-err-handler
to accept aBUFFER
argument. This is necessary so that they can show an overlay in the correct buffer.eval-error
event where we know for sure this is an error (also, the message now comes from the analyzed exception and not from stderr).analyze...
request.Possible complications
nrepl-err-handler
and relative functions' signature can break setups where users for some reason supply custom functions for this. I don't know if CIDER ever encouraged that – this stuff is quite low-level.