-
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
Separate UI code and business logic for the resolve and verify commands #4798
Separate UI code and business logic for the resolve and verify commands #4798
Conversation
…into compilationEvents
0fe3d68
to
cd53845
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.
Reviewed most of the rest, just need to have a final look at the core changes in a third batch.
Source/IntegrationTests/TestFiles/LitTests/LitTest/doofiles/Test2.dfy.expect
Show resolved
Hide resolved
Source/IntegrationTests/TestFiles/LitTests/LitTest/cli/projectFile/projectFile.dfy.expect
Show resolved
Hide resolved
/// <summary> | ||
/// Initializes a new symbol table with the given compilation unit. | ||
/// </summary> | ||
/// <param name="program">The parsed dafny program.</param> | ||
/// <param name="compilationUnit">The compilation to create the symbol table of.</param> | ||
/// <param name="cancellationToken">A token to cancel the update operation before its completion.</param> | ||
/// <returns>A symbol table representing the provided compilation unit.</returns> | ||
/// <exception cref="System.OperationCanceledException">Thrown when the cancellation was requested before completion.</exception> | ||
/// <exception cref="System.ObjectDisposedException">Thrown if the cancellation token was disposed before the completion.</exception> |
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 delete this comment?
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.
Was it useful? I would argue it's not. Even the interface ISymbolTableFactory
is unused.
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.
Given the amount of refactoring going on I just wanted to make sure it wasn't dropped by accident. If you removed it because it wasn't useful that's just fine (and looking closer I agree it seems like "I'm being forced to document every method" rather than "I have something useful to say beyond the signature here" :)
/// <summary> | ||
/// Publishes the diagnostics of the specified dafny document to the connected LSP client. | ||
/// </summary> | ||
/// <param name="state">The document whose diagnostics should be published.</param> |
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.
Ditto
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'll add it back.
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.
A few more for today. :)
if (Options.UseStdin) { | ||
var uri = new Uri("stdin:///"); | ||
result.Add(DafnyFile.CreateAndValidate(errorReporter, fileSystem, Options, uri, Token.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.
OoC what happens if you specify this in the IDE?
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.
You can't. StdIn
is not an IDE option.
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.
Sure, I'm just pointing out that technically you could add --stdin
to the Language Server Launch Args, and now it seems like it would actually do something rather than being ignored. I assume it's harmless but asking just in case.
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 you use the CLI to specify an option to a command to which it doesn't apply, then the CLI will return an error. If you pass it through the project file, the CLI won't error but it'll be ignored.
Source/DafnyDriver/CliCompilation.cs
Outdated
} catch (TaskCanceledException) { | ||
} |
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.
When does this happen? Why not catch OperationCanceledException instead?
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.
Added a comment and changed to OperationCanceledException
. Happens when there's a parsing error so it can not resolve. I could change the interface of Compiler
so it uses null
instead of OperationCanceledException
when it can't return a Task<ResolutionResult
due to a user program error. This means inside Compilation
there's a need for more if (resultIDependOn == null) { return null }
since we don't coast on the monadic behavior of async/await, but it might be more readable.
I'd prefer doing that in a follow-up PR, this one is huge as is.
if (HasErrors) { | ||
throw new OperationCanceledException(); | ||
} |
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.
The name of this exception always bothered/confused me a little, and it feels worse using it in the CLI context now: it sounds like the user cancelled the pipeline, when it's really that the pipeline stoped early because of errors.
Is this usage idiomatic for C# in general when hitting an "early return" as opposed to a parallel cancellation request?
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.
See above answer
Co-authored-by: Robin Salkeld <[email protected]>
… into mergedCLiAndServer
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.
Last batch!
|
||
public Task<ResolutionResult> Resolution => Compilation.Resolution; | ||
|
||
public void Start() { |
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.
Worth raising an explicit error if Start is called more than once?
new DafnyProgramVerifier(factory.CreateLogger<DafnyProgramVerifier>()), engine, input); | ||
} | ||
|
||
public async Task VerifyAllAndPrintSummary() { |
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.
Similarly, can we raise an error if Start wasn't called yet? Otherwise I assume you just get deadlock.
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.
In theory you can first call VerifyAllAndPrintSummary
but not await the result, then call Start
, and then await the task, although I don't see why you would do that.
foreach (var canVerify in orderedCanVerifies) { | ||
canVerifyResults[canVerify] = new CliCanVerifyResults(); | ||
await Compilation.VerifyCanVerify(canVerify, false); | ||
} |
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.
Would it be more efficient/cleaner to do await Task.WhenAll(...)
instead in general, rather than awaiting inside a loop like this?
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 have to await before I do the next call, to ensure that the previous verification work has finished being queued. Otherwise, the order in which things get verified becomes non-deterministic.
this.logger = logger; | ||
this.innerLogger = innerLogger; | ||
this.telemetryPublisher = telemetryPublisher; | ||
} | ||
|
||
private readonly ResolutionCache resolutionCache = new(); | ||
public CompilationUnit ResolveSymbols(DafnyProject project, Program program, CancellationToken cancellationToken) { | ||
public void ResolveSymbols(Compilation compilation, Program program, CancellationToken cancellationToken) { |
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 feels redundant to be passing in a program separately from the compilation here (and probably other similar places). Does it have to be the same as compilation.programAfterParsing
?
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 is a mutating operation, and program
is the program to mutate. It's a bit much for this code to assume which Program
field that Compilation
exposes it may mutate. For example, compilation.programAfterParsing
would be the wrong one to mutate ! That's an instance of Program
which is just the parsed program and which won't be changed afterwards.
var compilationUnit = resolutionResult.HasErrors | ||
? new CompilationUnit(input.Project.Uri, resolutionResult.ResolvedProgram) | ||
: new SymbolDeclarationResolver(logger, cancellationToken).ProcessProgram(input.Project.Uri, resolutionResult.ResolvedProgram); | ||
// telemetryPublisher.PublishTime("LegacyServerResolution", project.Uri.ToString(), DateTime.Now - beforeLegacyServerResolution); TODO |
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.
TODO
@@ -139,4 +161,379 @@ public record IdeState( | |||
public IEnumerable<Uri> GetDiagnosticUris() { | |||
return StaticDiagnostics.Keys.Concat(VerificationResults.Keys); | |||
} | |||
|
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 assume all the code from this point on is relocated rather than brand new? If not, how's the testing coverage?
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 is relocated, yes
@@ -111,7 +111,7 @@ | |||
// CHECK: ProofDependencies.dfy\(337,12\)-\(337,16\): requires clause | |||
// CHECK: ProofDependencies.dfy\(338,11\)-\(338,15\): ensures clause | |||
// CHECK: ProofDependencies.dfy\(341,3\)-\(341,39\): function call result | |||
// CHECK: ProofDependencies.dfy\(341,3\)-\(341,3\): function precondition satisfied |
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.
Not seeing why this location changed, was it actually incorrect before?
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'm not sure we have enough consistency in our reporting to say what's correct or not, but previously it reported a "range" which was just twice the starting position of the call, and now the range is the entire call expression.
- Fix: verification in the IDE no longer fails for iterators | ||
- Fix: the IDE now provides feedback when verification fails to run, for example due to a bad solver path | ||
- Fix: let the IDE correctly use the solver-path option when it's specified in a project file | ||
- Feat: improve the order of verification diagnostics emitted by the Dafny CLI, so that they now always follow the line order of the program. |
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.
❤️
1ca98aa
to
59d0b39
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.
A lot of work but well worth it, thanks!
Changes
At the heart of this PR is an architectural change: to lay the foundation for separating UI code from business logic. Part of this is to use Boogie purely as an API, instead of as a library that produces textual output for the CLI.
This change is not necessary, but I believe it is a good step towards giving us more control over the UI experience of our CLI, and to make it easier to work with our codebase. In the future section down below are some ideas of what this PR enables.
Because there is a lot of UI code intermingled with a lot of business code, making this architectural change produces a lot of code changes, so this PR only takes the first step: the pathway where UI and logic are separated is only used when using
dafny resolve
ordafny verify
. I will update the remaining commands in follow-up PRs.Refactoring
Compilation
, a UI agnostic back-end for driving the Dafny compiler pipeline, from the projectLanguageServer
toDafnyCore
, so it can be used by the CLI as well. Reusing this code means more IDE code is tested using the CLI test, and you can see the effect of this additional testing from the IDE fixes down below, yay!CliCompilation
which uses the CLI as a user interface to track the results ofCompilation
.dafny resolve
anddafny verify
useCliCompilation
instead ofCompilerDriver
.CliCompilation
, renameCompilerDriver
toSynchronousCliCompilation
, even though there are many featuresCliCompilation
does not support yet.DafnySymbolKind
from DafnyCore, which was a duplicate of an LSP enum that is now directly accessible in DafnyCoreBehavior
IDE fix: enable comma's in the library flag to specify multiple libraries, as the CLI does
IDE fix: enable having the same file as input multiple times, as the CLI does
IDE fix: support verification for some code generating Dafny constructors, like iterators
IDE fix: the option solver-path is respected by the IDE
IDE fix: when verification can not start due to a technical issue, the IDE tells the user what happened
Make it easy to update .expect files by setting the flag
DiffCommand.UpdateExpectFile
to true. This was broken before this PR.Configure littish tests so they time out after running for 120 minutes. They normally complete in less than 30m on all platforms, so 120m should be ample.
Fix a bug in the cloning code, that was discovered because the CLI tests are using the server back-end code, which clones the program after parsing. (Is this good or bad? In the future I hope we'll have an immutable AST so then the question becomes redundant)
Make the order in which verification error are shown on the CLI consistent. Now errors are always shown in the order in which they occur in the file
Example diff:
Future
This PR paves the way for:
--filter
option that operates on Dafny symbols/files, and possibly also filters parsing and resolutionDafny program verifier finished with 21 verified, 0 errors
but the number 21 does not refer to anything visible to the user. It would be better to report the number of Dafny symbols that were verified.Dafny program verifier did not attempt verification
when runningdafny resolve
How has this been tested?
By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.