-
Notifications
You must be signed in to change notification settings - Fork 262
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
feat: Read clauses on methods #4440
Conversation
…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
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.
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; |
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.
Seems to be some duplication between Function and Method that this PR increases, that we could reduce some time.
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.
Agreed, but I couldn't see an easy way without a fair bit of refactoring.
Source/DafnyCore/DafnyOptions.cs
Outdated
|
||
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); |
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 good, but it might be nice to also mention the reads *
default, since the value/verbosity ratio is high.
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.
Yes I agree on that
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.
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)) { |
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 think this code becomes obsolete by assigning the default reads *
during parsing
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.
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 *
.
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.
Wouldn't you reject that code as part of parsing as well ?
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.
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 |
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 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"); |
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 so little use of var
?
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.
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 => { |
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.
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
.
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.
Good observation, it makes sense
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.
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<...>
?)
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.
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? |
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
private void AddMethodOverrideReadsSubsetChk(Method m, BoogieStmtListBuilder builder, ExpressionTranslator etran, List<Variable> localVariables, | ||
Dictionary<IVariable, Expression> substMap, | ||
Dictionary<TypeParameter, Type> typeMap) { | ||
//getting framePrime |
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 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, |
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.
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, |
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 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.
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.
Yup I'll first seem if I can merge the two, and either way clean them up.
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.
Initial review, will continue later
Source/DafnyCore/DafnyOptions.cs
Outdated
|
||
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); |
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.
Yes I agree on that
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 => { |
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.
Good observation, it makes sense
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 think that after my comments it's ready for approval!
Good job.
…d-clauses-on-methods # Conflicts: # Source/DafnyCore/Resolver/PreType/UnderspecificationDetector.cs # Source/DafnyCore/Verifier/Translator.ExpressionWellformed.cs
…d-clauses-on-methods
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) |
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 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?
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.
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.
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.
As far as I'm concerned and desired this feature, this is all I need for now, thank you very much!
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>
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>
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 thereads
andmodifies
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 areads *
context (the default for methods), theExpressionTranslator.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.