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

feat: Read clauses on methods #4440

Merged
merged 59 commits into from
Aug 30, 2023

Conversation

robin-aws
Copy link
Member

@robin-aws robin-aws commented Aug 18, 2023

Implements dafny-lang/rfcs#6.

This functionality is guarded by a new --reads-clauses-on-methods option, even though it's almost completely backwards compatible: the only wrinkle is function-by-methods, where valid code today might be rejected when the reads clause of the function definition is applied to the by-method body.

Note that "methods" here is interpreted as it is in the Dafny reference manual and similar documentation: it includes constructors and ghost methods, but NOT lemmas.

The {:concurrent} attribute, which previously only existed to generate an auditor warning that Dafny could not verify this property, now creates assertions that the reads and modifies clauses on the function or method are empty.

The core implementation strategy is relatively straightforward: enabling reads checks (via the WFOptions value) in the well-formedness check for statements as well as expressions. The existing $_Frame variable is now split into $_ReadsFrame and $_ModifiesFrame, since we now need both at once in method contexts. To help make reads checks optional based on the new option, and to optimize by not enabling them when in a reads * context (the default for methods), the ExpressionTranslator.readsFrame field may be null. I performed some mild refactoring to make this cleaner but open to suggestion on further improvements.

Documentation changes preview at https://robin-aws.github.io/dafny/.

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

…d-clauses-on-methods

# Conflicts:
#	Source/Dafny/AST/DafnyAst.cs
#	Source/Dafny/Cloner.cs
#	Source/Dafny/Resolver.cs
#	Source/Dafny/Rewriter.cs
#	Source/Dafny/Verifier/Translator.ExpressionTranslator.cs
#	Source/DafnyCore/Dafny.atg
#	Source/DafnyCore/Rewriters/RefinementTransformer.cs
#	Source/DafnyCore/Verifier/Translator.TrStatement.cs
#	Source/DafnyCore/Verifier/Translator.cs
…d-clauses-on-methods

# Conflicts:
#	Source/DafnyCore/AST/Members/Method.cs
#	Source/DafnyCore/Resolver/ModuleResolver.cs
#	Source/DafnyCore/Rewriters/OpaqueMemberRewriter.cs
@robin-aws robin-aws changed the title feat: Read clauses on methods (WIP) feat: Read clauses on methods Aug 25, 2023
@robin-aws robin-aws marked this pull request as ready for review August 25, 2023 23:22
Copy link
Member

@keyboardDrummer keyboardDrummer left a comment

Choose a reason for hiding this comment

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

Whoa, so few changes are needed. Nice!

@@ -22,6 +21,7 @@ public class Method : MemberDecl, TypeParameter.ParentType,
public readonly List<Formal> Ins;
public readonly List<Formal> Outs;
public readonly List<AttributedExpression> Req;
public readonly List<FrameExpression> Reads;
Copy link
Member

Choose a reason for hiding this comment

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

Seems to be some duplication between Function and Method that this PR increases, that we could reduce some time.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, but I couldn't see an easy way without a fair bit of refactoring.


RegisterLegacyUi(CommonOptionBag.ReadsClausesOnMethods, ParseBoolean, "Language feature selection", "readsClausesOnMethods", @"
0 (default) - Reads clauses on methods are forbidden.
1 - Reads clauses on methods are permitted.".TrimStart(), defaultValue: false);
Copy link
Member

Choose a reason for hiding this comment

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

This is good, but it might be nice to also mention the reads * default, since the value/verbosity ratio is high.

Copy link
Member

Choose a reason for hiding this comment

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

Yes I agree on that

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call, I added this to the new CLI help string too.

@@ -792,9 +792,25 @@ public partial class ModuleResolver {
new ActualBindings(f.Formals.ConvertAll(Expression.CreateIdentExpr)).ArgumentBindings,
tok);
var post = new AttributedExpression(new BinaryExpr(tok, BinaryExpr.Opcode.Eq, r, fn));
List<FrameExpression> reads;
if (Options.Get(CommonOptionBag.ReadsClausesOnMethods)) {
Copy link
Member

Choose a reason for hiding this comment

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

I think this code becomes obsolete by assigning the default reads * during parsing

Copy link
Member Author

Choose a reason for hiding this comment

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

That would make it hard/impossible to reject explicit reads clauses if the option is not turned on though, since you couldn't distinguish between code that explicitly includes reads *.

Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't you reject that code as part of parsing as well ?

Copy link
Member Author

Choose a reason for hiding this comment

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

On revisiting this, yes, I think I could have assigned the default during parsing instead (with the same conditionals to avoiding doing that on lemmas etc.) I was wary of that at the time since I didn't see any other examples of adding default elements at parse time, and didn't want to risk more cases of errors that pointed to tokens that didn't exist in the original program, although in this particular case I don't think that's possible (since reads * can't cause errors AFAICT).

@@ -204,9 +204,17 @@ public class ExpectContracts : IRewriter {
var post = new AttributedExpression(new BinaryExpr(tok, BinaryExpr.Opcode.Eq, r, fn) {
Type = Type.Bool
});
// If f.Reads is empty, replace it with an explicit `reads {}` so that we don't replace that
Copy link
Member

Choose a reason for hiding this comment

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

I think this code becomes obsolete by assigning the default reads * during parsing

Boogie.LocalVariable frame = new Boogie.LocalVariable(tok, new Boogie.TypedIdent(tok, null ?? traitFrame.Name, traitFrame.Type));
localVariables.Add(frame);
// $_Frame := (lambda<alpha> $o: ref, $f: Field alpha :: $o != null && $Heap[$o,alloc] ==> ($o,$f) in Reads-Clause);
Boogie.TypeVariable alpha = new Boogie.TypeVariable(tok, "alpha");
Copy link
Member

Choose a reason for hiding this comment

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

Why so little use of var ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Again copied without cleaning up, will do if it makes sense (prefer to merge these two instead)

var desc = new PODesc.IsAllocated("default value", "in the two-state lemma's previous state");
builder.Add(Assert(e.tok, wh, desc));
// check well-formedness of any default-value expressions (before assuming preconditions)
readsCheckDelayer.WithDelayedReadsChecks(true, wfo => {
Copy link
Member

Choose a reason for hiding this comment

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

AFAIK the prefix With is commonly used for pure methods, while this seems to be a side-effecting one. I suggest replacing With with Add.

Copy link
Member

Choose a reason for hiding this comment

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

Good observation, it makes sense

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair point, although I do want to get across the idea of "execute this with this additional context/before and after effect". Perhaps RunWithDelayedReadsChecks (or is there a more idiomatic verb for executing an Action<...>?)

Copy link
Member Author

Choose a reason for hiding this comment

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

Went with "DoWith.." :)

@@ -1334,7 +1370,58 @@ public partial class Translator {
builder.Add(Assert(original.Tok, decrChk, new PODesc.TraitDecreases(original.WhatKind)));
}

private void AddMethodOverrideSubsetChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List<Variable> localVariables,
// TODO: Worth sharing code between this and AddMethodOverrideModifiesSubsetChk?
Copy link
Member

Choose a reason for hiding this comment

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

TODO

private void AddMethodOverrideReadsSubsetChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List<Variable> localVariables,
Dictionary<IVariable, Expression> substMap,
Dictionary<TypeParameter, Type> typeMap) {
//getting framePrime
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand this comment.

@@ -1334,7 +1370,58 @@ public partial class Translator {
builder.Add(Assert(original.Tok, decrChk, new PODesc.TraitDecreases(original.WhatKind)));
}

private void AddMethodOverrideSubsetChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List<Variable> localVariables,
// TODO: Worth sharing code between this and AddMethodOverrideModifiesSubsetChk?
private void AddMethodOverrideReadsSubsetChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List<Variable> localVariables,
Copy link
Member

Choose a reason for hiding this comment

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

m => subject/target/method

@@ -1334,7 +1370,58 @@ public partial class Translator {
builder.Add(Assert(original.Tok, decrChk, new PODesc.TraitDecreases(original.WhatKind)));
}

private void AddMethodOverrideSubsetChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List<Variable> localVariables,
// TODO: Worth sharing code between this and AddMethodOverrideModifiesSubsetChk?
private void AddMethodOverrideReadsSubsetChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List<Variable> localVariables,
Copy link
Member

Choose a reason for hiding this comment

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

Could you clean up the code in the method AddMethodOverrideReadsSubsetChk a bit? It shows up as new in this PR but I see it was existing code.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup I'll first seem if I can merge the two, and either way clean them up.

Copy link
Member

@MikaelMayer MikaelMayer left a comment

Choose a reason for hiding this comment

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

Initial review, will continue later

Source/DafnyCore/AST/Members/Method.cs Show resolved Hide resolved
Source/DafnyCore/AST/Members/Method.cs Outdated Show resolved Hide resolved

RegisterLegacyUi(CommonOptionBag.ReadsClausesOnMethods, ParseBoolean, "Language feature selection", "readsClausesOnMethods", @"
0 (default) - Reads clauses on methods are forbidden.
1 - Reads clauses on methods are permitted.".TrimStart(), defaultValue: false);
Copy link
Member

Choose a reason for hiding this comment

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

Yes I agree on that

Source/DafnyCore/Verifier/ProofObligationDescription.cs Outdated Show resolved Hide resolved
var desc = new PODesc.IsAllocated("default value", "in the two-state lemma's previous state");
builder.Add(Assert(e.tok, wh, desc));
// check well-formedness of any default-value expressions (before assuming preconditions)
readsCheckDelayer.WithDelayedReadsChecks(true, wfo => {
Copy link
Member

Choose a reason for hiding this comment

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

Good observation, it makes sense

Source/DafnyCore/Verifier/Translator.ClassMembers.cs Outdated Show resolved Hide resolved
Source/DafnyCore/Verifier/Translator.ClassMembers.cs Outdated Show resolved Hide resolved
Copy link
Member

@MikaelMayer MikaelMayer left a comment

Choose a reason for hiding this comment

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

I think that after my comments it's ready for approval!
Good job.

Source/DafnyCore/Verifier/Translator.cs Outdated Show resolved Hide resolved
Source/DafnyCore/Verifier/Translator.cs Outdated Show resolved Hide resolved
docs/DafnyRef/Specifications.md Outdated Show resolved Hide resolved
docs/DafnyRef/Specifications.md Outdated Show resolved Hide resolved
docs/DafnyRef/Specifications.md Outdated Show resolved Hide resolved
Source/DafnyDriver/DafnyDoc.cs Show resolved Hide resolved
Source/DafnyCore/Dafny.atg Show resolved Hide resolved
Test/dafny0/ConcurrentAttribute.dfy Show resolved Hide resolved
Test/dafny0/ConcurrentAttribute.dfy.expect Outdated Show resolved Hide resolved
Test/dafny0/ReadsOnMethods-Resolution.dfy.expect Outdated Show resolved Hide resolved
ConcurrentAttribute.dfy(30,25): Error: reads clause could not be proved to be empty ({:concurrent} restriction)
ConcurrentAttribute.dfy(48,23): Error: reads clause could not be proved to be empty ({:concurrent} restriction)
ConcurrentAttribute.dfy(51,23): Error: reads clause could not be proved to be empty ({:concurrent} restriction)
ConcurrentAttribute.dfy(55,23): Error: reads clause could not be proved to be empty ({:concurrent} restriction)
Copy link
Member

Choose a reason for hiding this comment

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

I see that you recently deleted {:concurrent} checks from the auditor and various places within the last 13 commits, however, you still have issues mentioning it. Can you please explain?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah which issues, I'm not sure I follow? I intended to delete all the references to {:concurrent} in the auditor context since I removed that case and have replaced it with verification obligations instead, but I could have missed some.

Copy link
Member

@MikaelMayer MikaelMayer left a comment

Choose a reason for hiding this comment

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

As far as I'm concerned and desired this feature, this is all I need for now, thank you very much!

@robin-aws robin-aws merged commit 0d9d4e9 into dafny-lang:master Aug 30, 2023
18 checks passed
keyboardDrummer pushed a commit to keyboardDrummer/dafny that referenced this pull request Sep 15, 2023
Implements dafny-lang/rfcs#6.

This functionality is guarded by a new `--reads-clauses-on-methods`
option, even though it's almost completely backwards compatible: the
only wrinkle is function-by-methods, where valid code today might be
rejected when the reads clause of the function definition is applied to
the by-method body.

Note that "methods" here is interpreted as it is in the Dafny reference
manual and similar documentation: it includes constructors and ghost
methods, but NOT lemmas.

The `{:concurrent}` attribute, which previously only existed to generate
an auditor warning that Dafny could not verify this property, now
creates assertions that the `reads` and `modifies` clauses on the
function or method are empty.

The core implementation strategy is relatively straightforward: enabling
reads checks (via the `WFOptions` value) in the well-formedness check
for statements as well as expressions. The existing `$_Frame` variable
is now split into `$_ReadsFrame` and `$_ModifiesFrame`, since we now
need both at once in method contexts. To help make reads checks optional
based on the new option, and to optimize by not enabling them when in a
`reads *` context (the default for methods), the
`ExpressionTranslator.readsFrame` field may be null. I performed some
mild refactoring to make this cleaner but open to suggestion on further
improvements.

Documentation changes preview at https://robin-aws.github.io/dafny/.

<small>By submitting this pull request, I confirm that my contribution
is made under the terms of the [MIT
license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>
keyboardDrummer pushed a commit that referenced this pull request Sep 19, 2023
Implements dafny-lang/rfcs#6.

This functionality is guarded by a new `--reads-clauses-on-methods`
option, even though it's almost completely backwards compatible: the
only wrinkle is function-by-methods, where valid code today might be
rejected when the reads clause of the function definition is applied to
the by-method body.

Note that "methods" here is interpreted as it is in the Dafny reference
manual and similar documentation: it includes constructors and ghost
methods, but NOT lemmas.

The `{:concurrent}` attribute, which previously only existed to generate
an auditor warning that Dafny could not verify this property, now
creates assertions that the `reads` and `modifies` clauses on the
function or method are empty.

The core implementation strategy is relatively straightforward: enabling
reads checks (via the `WFOptions` value) in the well-formedness check
for statements as well as expressions. The existing `$_Frame` variable
is now split into `$_ReadsFrame` and `$_ModifiesFrame`, since we now
need both at once in method contexts. To help make reads checks optional
based on the new option, and to optimize by not enabling them when in a
`reads *` context (the default for methods), the
`ExpressionTranslator.readsFrame` field may be null. I performed some
mild refactoring to make this cleaner but open to suggestion on further
improvements.

Documentation changes preview at https://robin-aws.github.io/dafny/.

<small>By submitting this pull request, I confirm that my contribution
is made under the terms of the [MIT
license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>
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.

3 participants