-
Notifications
You must be signed in to change notification settings - Fork 267
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
Feat: Better format in hover messages #3687
Conversation
- Added the trace of proof even in successes - Replaced intermediate "Could not prove" and "Did prove" by "Inside " to indicate this is just a trace - Use backticks to indicate quoted code - Better error message instead of "error is impossible: This is the precondition that might not hold" - First-class support of {:error} in hover messages.
Why do we show success messages? It seems like information the user doesn't need and that might be confusing because as a user I would expect failure messages, so I might interpret the success messages as such. Especially a message like "error is impossible" which has two bad sounding words in it, reads like an error. |
(description?.SuccessDescription ?? "_no message_"); | ||
case GutterVerificationStatus.Verified: { | ||
var successDescription = description?.SuccessDescription ?? "_no message_"; | ||
if (successDescription == "error is impossible: This is the precondition that might not hold.") { |
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.
Could you refer to the substrings from which this concatenated string originates?
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.
Could you refer to the substrings from which this concatenated string originates?
I actually found a way to get rid of this if statement by adding one more proof obligation description that ought to be implemented.
case GutterVerificationStatus.Verified: { | ||
var successDescription = description?.SuccessDescription ?? "_no message_"; | ||
if (successDescription == "error is impossible: This is the precondition that might not hold.") { | ||
successDescription = "the precondition always holds"; |
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.
If we have the concept of success descriptions, shouldn't they be defined together with the failure descriptions? This seems difficult to maintain.
|
||
if (!currentlyHoveringPostcondition && | ||
(failureDescription == new EnsuresDescription().FailureDescription)) { | ||
failureDescription = "a postcondition could not be proven on this return path"; |
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.
Why does this message need to be changed? Why can't the back-end emit the right message to begin with? I think it would be great if DafnyHoverHandler can be completely Dafny agnostic.
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.
Why does this message need to be changed? Why can't the back-end emit the right message to begin with? I think it would be great if DafnyHoverHandler can be completely Dafny agnostic.
Agree.
I created a new EnsuresDescription
in Dafny so that we don't need this work-around Boogie's EnsuresDescription
@@ -259,7 +274,7 @@ class AssertionBatchIndexComparer : IComparer<AssertionBatchIndex> { | |||
// however, nested postconditions should be displayed | |||
if (errorToken is BoogieRangeToken rangeToken && !hoveringPostcondition) { | |||
var originalText = rangeToken.PrintOriginal(); | |||
deltaInformation += " \n" + CouldProveOrNotPrefix + originalText; | |||
deltaInformation += " \n" + (token == null ? CouldProveOrNotPrefix : "Inside ") + "`" + originalText + "`"; |
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 is as well, why can't the back-end emit the proper message? This seems like it's introducing inconsistency between IDE and CLI
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 is as well, why can't the back-end emit the proper message? This seems like it's introducing inconsistency between IDE and CLI
This error message is specialized for the language server, as it assumes that the reader has access to markdown capabilities, and it also quotes the code that was/was not proven.
To the contrary, in the CLI, we use the term "Related position:" and only offer positions by default. We don't show code snippets for related positions. I wish we can change that but we'll keep that for another PR, as it will be another substantial change.
I agree that "error is impossible: X" should be replaced by a positive message. For custom error messages however, this is not possible. If you have a better rephrasing suggesting it would be good. To the question "Why do we show success messages", note that obviously, we only show them on hover. They serve multiple purpose.
Here is how displaying success messages could be better:
|
customErrMsg ?? "this postcondition might not hold on a return path"; | ||
|
||
public string FailureAtPathDescription => | ||
customErrMsg ?? "a postcondition could not be proven on this return path"; |
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.
Isn't this second line similar to PostconditionDescription.FailureDescription
?
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.
Isn't this second line similar to PostconditionDescription.FailureDescription ?
Yes, indeed, I added a redirection instead.
public override string FailureDescription => | ||
customErrMsg ?? "this postcondition might not hold on a return path"; | ||
|
||
public string FailureAtPathDescription => |
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 find the concept of having an at path
and a regular failure description not immediately intuitive.
Conceptually I view the verification failure as a stack trace. For example in:
predicate P(x : int) {
true
}
predicate Q(x : int) {
x == 2 // Stack(0)
}
method Foo() return (x: int)
ensures O(x) && Q(x) // Stack(1)
{
x := 1;
assert Foo.ensures() // Stack(2)
return;
}
I think in a previous discussion about squiggly lines, you explained that any part of the stack trace that falls outside of the method definition should not be taken into account, so for failing postconditions that only leaves the bottom 2 stack elements as relevant, and then I see it makes sense to have a failure description for both of those.
Feel free to ignore this comment, just me doing rubber duck analysis.
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.
Thanks for rubber duck analysis :-)
Your metaphor with stack trace is quite interesting, worth exploring from UX's perspective. And I think this PR is exploring it that way. Let me explain you why in the next paragraph.
The existence of "nested expressions" here is due only to the condition splitting at the Dafny level, that is, if we want to prove "A && B", it will prove "A" on one side and "A ==> B" (or just "B" where A is assumed actually) on the other side. If we want to prove P(x)
, we will prove each conjunct of P(x)
-- that's also why sometimes it's worth making predicate opaque, because if P(x)
has 100 conjuncts, proving P(y)
when x == y
can still be very costly -
This is why I chose to replace "Could not prove" (which was technically correct, If you can't prove A
, you can't prove A && B
) by "Inside", to better match the metaphor of the stack.
I think in a previous discussion about squiggly lines, you explained that any part of the stack trace that falls outside of the method definition should not be taken into account,
My point was that they should not be underlined permanently as errors because they are relevant only in the context of the failed assertion.
failureDescription = "this postcondition might not hold on a return path"; | ||
} | ||
|
||
if (!currentlyHoveringPostcondition && |
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.
Is it not possible to get the description that matches the diagnostic/related location of where the cursor is? Like, if you are hovering over the postcondition, can we not find the description that says "This is the postcondition that might not hold.", and if we hover of the return statement, the one that says "A postcondition might not hold on this return path." ?
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.
Actually, if hovering a postcondition was saying "This is the postcondition that might not hold.", it would feel weird to me, because it feels like there was a sentence referring to the postcondition before this one that this sentence answers. Otherwise, the error message would just say "This postcondition might not hold".
And this is actually the case. The message "This is the postcondition that might not hold." was designed to appear as a relative error message in the diagnostics, just below "A postcondition might not hold on this return path".
This is why we have a different error message when hovering the postcondition by itself.
Now you could try to make a single error message for both cases, one that is relative to the error path, and the other one that is considering the postcondition only by itself. That message could be "This postcondition might not hold".
However, I'm considering the user's experience, I really liked "This is the postcondition that might not hold better" better, so that mean I had to specialize the other messagE.
: $"error is impossible: {customErrMsg}"; | ||
|
||
public override string FailureDescription => | ||
customErrMsg ?? "this postcondition might not hold on a return path"; |
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 text surely already occurs somewhere else in the code base right? Can you reuse that text?
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 appears in Boogie. I would like to break the dependency for the error message.
public override string SuccessDescription => | ||
customErrMsg is null | ||
? "this postcondition holds" | ||
: $"error is impossible: {customErrMsg}"; |
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.
Why not take a customSuccessMessage
as well? error is impossible:
sounds bad.
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.
Why not take a customSuccessMessage as well? error is impossible: sounds bad.
It took me some time to implement but I thought it was a nice idea.
I implemented (and documented it) using the same attribute {:error <error message>[, <success message>
}` and I dropped the string "error is impossible" entirely. I also refactored the code so that similar proof obligation descriptions use the same overriding mechanism.
I would like it if we had a document that discusses the UX design of this feature. Currently a user will often be presented with duplicate error messages when hovering, and success messages, like here: while I think in most cases the user would just like to see the bottom part: I trust that you've thought hard about this and that there's a good chance this is a great solution, at least at the moment, but I think it would be good to have in writing the thought process and considered alternatives, because this UI seems like it compromises on succinctness. Would it help to only show the assertion for which we're displaying the resources? Why do we also need to display error/success messages there? If there is an assertion mentioned for which resources are computed, and there is no error for it, doesn't that sufficiently imply Dafny verified it? Did we also consider grouping by batch, so the resource usage of each batch is only shown once instead of for each assertion in the batch? |
# Conflicts: # Source/DafnyLanguageServer.Test/Lookup/HoverVerificationTest.cs
…t text Fixed CI tests
# Conflicts: # Source/DafnyLanguageServer/Language/DiagnosticErrorReporter.cs
… feat-better-error-messages # Conflicts: # Source/DafnyLanguageServer/Language/DiagnosticErrorReporter.cs
Fixes #3686
{:error}
in failed postconditions.Acts as a precursor to #3324 by harmonizing the change made in boogie "might not hold" => "could not be proved" in preconditions and postconditions
The screenshots of the issue are now fixed w.r.t. the described errors.
With support for
{:error}
in postconditionsBy submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.