Skip to content
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

Implement decreases to expressions #5367

Merged
merged 40 commits into from
May 29, 2024
Merged

Implement decreases to expressions #5367

merged 40 commits into from
May 29, 2024

Conversation

atomb
Copy link
Member

@atomb atomb commented Apr 25, 2024

Description

Implement the decreases to and nonincreases to expression forms. This allows you to state that the left-hand sequence of expressions decreases (or at least doesn't increase) to the right-hand sequence.

One caveat: for now, decreases to is allowed only inside parentheses, to avoid parsing ambiguities. This is a strict subset of what we'd ideally like to support. I think it should be possible to allow it to appear elsewhere as part of a separate, backward-compatible PR.

Fixes #5252

How has this been tested?

Added dafny0/DecreasesTo{1,2,3,4,5}.dfy

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

Things remaining:
* Better syntax
* Fill in error messages
* Ensure correct parameters to DecreasesCheck
* Figure out why most proof obligation descriptions aren't really useful
This has shortcomings, and in particular is only allowed in `assert`
statements at the moment. Making it fully general may require some more
parser refactoring.
@MikaelMayer
Copy link
Member

Having looked only at the test, I like the design so far. Could you also include something like ambiguous parsing tests, such as tuples assert (true, true decreases to false) == true; or assert (true, true decreases to false) == (true, true); depending on your parsing strategy?

@atomb atomb marked this pull request as ready for review May 23, 2024 22:43
Copy link
Collaborator

@fabiomadge fabiomadge left a comment

Choose a reason for hiding this comment

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

There are a couple of instances where you can remove whitespace, but I'm mostly happy with the rest. Unfortunately, I'm not of much help when it comes to the parser.

When proving that a loop or recursive callable terminates, Dafny
automatically generates a proof obligation that the sequence of
expressions listed in a `decreases` clause gets smaller (in the
termination ordering) with each iteration or recursive call. Normally,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe add that it's a lexicographical ordering?

DecreasesTo2.dfy(5,24): Error: invalid UnaryExpression
DecreasesTo2.dfy(9,21): Error: ident expected
DecreasesTo2.dfy(9,12): Error: expected 'to'
DecreasesTo2.dfy(13,10): Error: invalid ParensExpression
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would love to see more helpful error messages, but they're just as good as the ones for other operators, so that's what we probably have to settle for.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I agree, on both sides.

Comment on lines 67 to 72

/*
method BadTypes () {
assert (true decreases to 0);
}
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/*
method BadTypes () {
assert (true decreases to 0);
}
*/

}

method BadInstance5(i: int, b: bool) {
assert (i decreases to b);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't the resolver catch this?

Copy link
Member Author

Choose a reason for hiding this comment

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

I originally thought so, too, but I think there's a good reason for it not to. When Dafny compares two tuples of expressions for termination ordering, it requires only that a prefix of those expression even be comparable. So something like x, 0 decreases to x-1, false should succeed (and does, in the current implementation -- maybe I should add a test showing that). To make this general, the comparable prefix can be empty, in which case the decreases to expression will always be false.

@@ -1,4 +1,6 @@
namespace Microsoft.Dafny.LanguageServer.Language {
using Microsoft.Boogie;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
using Microsoft.Boogie;

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like this one is actually used.

@@ -1165,6 +1166,13 @@ public partial class ModuleResolver {

} else if (expr is NestedMatchExpr nestedMatchExpr) {
ResolveNestedMatchExpr(nestedMatchExpr, resolutionContext);

Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change

Comment on lines +1196 to +1217
} else if (expr is DecreasesToExpr decreasesToExpr) {
var comma = false;
foreach (var oldExpr in decreasesToExpr.OldExpressions) {
if (comma) {
wr.Write(", ");
}
PrintExpression(oldExpr, false);
comma = true;
}
if (decreasesToExpr.AllowNoChange) {
wr.Write(" nonincreases to ");
} else {
wr.Write(" decreases to ");
}
comma = false;
foreach (var newExpr in decreasesToExpr.NewExpressions) {
if (comma) {
wr.Write(", ");
}
PrintExpression(newExpr, false);
comma = true;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we test this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep. See the Asserted expression: lines in the added tests.

@@ -144,6 +144,7 @@ TOKENS
by = "by".
in = "in".
decreases = "decreases".
nonincreases = "nonincreases".
Copy link
Collaborator

Choose a reason for hiding this comment

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

Now that it's there, we might as well keep it, but was there a strong reason to add it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sometimes the check that Dafny needs to perform is, roughly speaking, <= instead of <. To be able to express those conditions, it seemed most natural to have a form of the expression that encoded <= instead of <. It would be possible, alternatively, to have (x decreases to y) || x == y, but it seems a little more awkward, and I think would be more difficult to implement, as well (though I'm no longer sure of that).

@@ -2919,7 +2920,7 @@ AssertStmt<out Statement/*!*/ s>
BlockStmt<out proof, out proofStart, out proofEnd>
| ";"
| (.
SemErr(ErrorId.p_assert_needs_by_or_semicolon, t, "expected either 'by' or a semicolon following the assert expression");
SemErr(ErrorId.p_assert_needs_by_or_semicolon, t, $"expected either 'by' or a semicolon following the assert expression");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
SemErr(ErrorId.p_assert_needs_by_or_semicolon, t, $"expected either 'by' or a semicolon following the assert expression");
SemErr(ErrorId.p_assert_needs_by_or_semicolon, t, "expected either 'by' or a semicolon following the assert expression");

Comment on lines 3206 to 3209
Expression<out Expression e, bool allowLemma, bool allowLambda, bool allowBitwiseOps = true>
=
NotDecreasesToExpression<out e, allowLemma, allowLambda, allowBitwiseOps>
.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I have no idea how to improve on that, but making this the top level seems like a big change.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, and now that you mention it this change isn't necessary anymore. I made it to help with switching certain things back and forth during debugging, but in the final version I don't think it's necessary. I'll try to remove it.

Copy link
Contributor

@alex-chew alex-chew left a comment

Choose a reason for hiding this comment

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

Looks good to me!

@atomb atomb requested a review from fabiomadge May 28, 2024 15:59
@atomb atomb enabled auto-merge (squash) May 28, 2024 16:22
@atomb atomb merged commit 4eb179e into dafny-lang:master May 29, 2024
21 checks passed
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.

Allow termination ordering to be written in expressions
4 participants