Skip to content

feat: Compute triggers for such-that operations #6023

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

Merged
merged 70 commits into from
Feb 25, 2025

Conversation

RustanLeino
Copy link
Collaborator

@RustanLeino RustanLeino commented Jan 8, 2025

This PR computes triggers for :| operations ("assign such that", "let such that", binding if statements, and binding if case statements). It also improves the logic used to guess witnesses.

Main new features

  • Compute trigger for all :| constructs
  • For if and if-case statements with :|, output warning if there is no trigger
  • For assign-such-that statements and let-such-that expressions, do not output warning, but if the proof to establish a witness fails, then include a hint in the error message that the failure may have stemmed from that no trigger was found
  • In all :| constructs, support the same trigger attributes as for quantifiers, namely:
    • allow {:trigger <exprs>} to manually specify a trigger
    • allow {:trigger} to suppress any warning
    • allow {:nowarn} to change the warning into an information message

Related new features

A proof obligation associated with assign-such-that and let-such-that constructs x :| P(x) is to show that such an x exists. The general form of this is the proof obligation exists x :: P(x). However, Dafny also allows the proof obligation to be established from candidate witnesses that the verifier guesses. Thus, the proof obligation may have several disjuncts, for example:

P(0) || P(1) || exists x :: P(x)

This PR improves this machinery further; specifically:

  • come up with a guess in the event that the type of x is a map
  • in the event that P(x) has the form x in map[...] for some map display map[...], use the given keys as guesses (just like is done for other collection displays)
  • distribute any initial disjuncts of P(x) that don't mention x to outside the existential, so that instead of exists x :: R || P(x), the proof obligation is R || exists x :: P(x) (for soundness, this is done only if the type of x is nonempty)

Notes for reviewers

The PR also contains smaller changes, namely:

  • Change and/or/implies creation simplification to do more simplifications and to respect short-circuit evaluation.
  • Allow BoundVar creation to indicate ghost status
  • Fix pretty printing of guarded if statements to print the original :| syntax in the guard, rather than its desugaring
  • Add LiteralExpr.IsFalse method
  • Fix Java compiler to emit a necessary cast for map displays
  • Fix compiler to support compilation of BlockByProofStmt statements
  • Improve C# code in various places
  • Fix typo in warning message
  • Given a map type in compiler method Zero, return an empty map
  • Let GuessWitnesses make more use of the Zero method

And regarding tests:

  • The tests for the new features are found in triggers/TriggersForSuchThat.dfy (also relevant: wishlist/assign-such-that-antecedent.dfy)
  • Many other test were updated to new expected output (in most cases to fix a typo in a warning message) or to add proof steps now that the :| constructs have specific triggers

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

# Conflicts:
#	Source/DafnyCore/Triggers/QuantifiersCollector.cs
# Conflicts:
#	Source/DafnyCore/AST/Expressions/Comprehensions/ExistsExpr.cs
#	Source/DafnyCore/AST/Substituter.cs
#	Source/DafnyCore/Dafny.atg
#	Source/DafnyCore/Triggers/ComprehensionTriggerGenerator.cs
#	Source/DafnyCore/Verifier/BoogieGenerator.ExpressionWellformed.cs
#	Source/DafnyCore/Verifier/BoogieGenerator.cs
#	Source/DafnyCore/Verifier/Statements/BoogieGenerator.TrStatement.cs
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/auditor/TestAuditor.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/CoinductiveProofs.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/Comprehensions.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/ComprehensionsNewSyntax.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/DirtyLoops.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/LetExpr.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/snapshots/Snapshots5.run.legacy.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-1207.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-1545.dfy.refresh.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-897.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/triggers/emptyTrigger.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/triggers/loop-detection-messages--unit-tests.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/triggers/some-terms-do-not-look-like-the-triggers-they-match.dfy.expect
#	Source/IntegrationTests/TestFiles/LitTests/LitTest/wishlist/assign-such-that-antecedent.dfy.expect
@RustanLeino RustanLeino disabled auto-merge January 15, 2025 19:56
@RustanLeino RustanLeino enabled auto-merge (squash) January 15, 2025 19:58
Copy link
Member

@cpitclaudel cpitclaudel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently reading through the changes. The core changes look good to me; I've left some suggestions but nothing blocking. The rest I'm currently reading through.

cpitclaudel
cpitclaudel previously approved these changes Feb 21, 2025
Copy link
Member

@cpitclaudel cpitclaudel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me; I've left comments but I don't think any of them require significant refactorings.

} else if (LiteralExpr.IsFalse(a) || LiteralExpr.IsTrue(b)) {
return a;
}
// Note, to respect evaluation order, we don't simplify "X && false" to "false".
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really a matter of evaluation order? What actually goes wrong with the relaxed criterion from before?
It could be side effets, but what side effects does an expression have?
Also, isn't that what the allowSimplification flag is for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call-out. I thought some more and agree that it's okay that, when requested by allowSimplification, include that simplication after all. The test suite seems to confirm this. (I applied the change to &&, ==>, and ||.)

@@ -76,7 +76,9 @@ method Correct2<W>(s: seq<W>) returns (ghost r: Option<W>)
}
var b := exists w: W :: P(w);
ghost var nonempty: W := s[0]; // this leads the verifier to see that W must be nonempty after all
ghost var w: W :| b ==> P(w);
ghost var w: W :| b ==> P(w) by {
var pw := P(nonempty); // introduce something to trigger quantification with
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we actually left :| alone (without triggers) on purpose back when we added them elsewhere, because it's not completely unreasonable to just try ambient values when we're trying to prove that a :| clause is inhabited (and indeed we do try to enumerate a few reasonable values by hand). That said I think that more stability is a reasonable price to pay here.

@RustanLeino
Copy link
Collaborator Author

Thanks for your detailed review, @cpitclaudel! I've made changes accordingly, so the PR is ready for your reapproval (if you like my changes).

Copy link
Member

@cpitclaudel cpitclaudel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, looks great now!
I agree that we can put the refactoring where the existential is explicit and :| is separate from := to some hypothetical future.

@RustanLeino RustanLeino merged commit 25285f9 into dafny-lang:master Feb 25, 2025
22 checks passed
@RustanLeino RustanLeino deleted the triggers-for-such-that branch February 25, 2025 23:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants