-
Notifications
You must be signed in to change notification settings - Fork 260
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: Support for the {:opaque}
attibute on const
#2545
Changes from 13 commits
b3500c7
1e90892
5d8d6d2
517e67d
d3ae216
86486da
2b14154
8e061c4
091cedd
513ace4
9f5f3d1
44cbd36
4f3025a
d56b3dd
68121d9
e1156fd
30f05af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -418,6 +418,7 @@ public void ResolveProgram(Program prog) { | |
refinementTransformer = new RefinementTransformer(prog); | ||
rewriters.Add(refinementTransformer); | ||
rewriters.Add(new AutoContractsRewriter(reporter, builtIns)); | ||
rewriters.Add(new OpaqueConstRewriter(this.reporter)); | ||
rewriters.Add(new OpaqueFunctionRewriter(this.reporter)); | ||
rewriters.Add(new AutoReqFunctionRewriter(this.reporter)); | ||
rewriters.Add(new TimeLimitRewriter(reporter)); | ||
|
@@ -11100,6 +11101,21 @@ public void ResolveStatement(Statement stmt, ResolutionContext resolutionContext | |
var call = new CallStmt(methodCallInfo.Tok, s.EndTok, new List<Expression>(), methodCallInfo.Callee, methodCallInfo.ActualParameters); | ||
s.ResolvedStatements.Add(call); | ||
} | ||
} else if (expr is NameSegment or ExprDotName) { | ||
if (expr is NameSegment) { | ||
ResolveNameSegment(expr as NameSegment, true, null, revealResolutionContext, true); | ||
} else { | ||
ResolveDotSuffix(expr as ExprDotName, true, null, revealResolutionContext, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For |
||
} | ||
MemberSelectExpr callee = (MemberSelectExpr)((ConcreteSyntaxExpression)expr).ResolvedExpression; | ||
if (callee == null) { | ||
} else if ((callee.Member is Lemma or TwoStateLemma && Attributes.Contains(callee.Member.Attributes, "axiom"))) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One of the outermost pair of parentheses is redundant. |
||
//The revealed member is a function | ||
reporter.Error(MessageSource.Resolver, callee.tok, "to reveal a function ({0}), append parentheses", callee.Member.ToString().Substring(7)); | ||
} else { | ||
var call = new CallStmt(expr.tok, s.EndTok, new List<Expression>(), callee, new List<ActualBinding>()); | ||
s.ResolvedStatements.Add(call); | ||
} | ||
} else { | ||
ResolveExpression(expr, revealResolutionContext); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -970,6 +970,55 @@ void AddHoverText(IToken tok, string format, params object[] args) { | |
} | ||
} | ||
|
||
public class OpaqueConstRewriter : IRewriter { | ||
public OpaqueConstRewriter(ErrorReporter reporter) : base(reporter) { | ||
} | ||
|
||
internal override void PreResolve(ModuleDefinition m) { | ||
foreach (var d in m.TopLevelDecls) { | ||
if (d is TopLevelDeclWithMembers) { | ||
ProcessOpaqueClassConsts((TopLevelDeclWithMembers)d); | ||
} | ||
} | ||
} | ||
|
||
private void ProcessOpaqueClassConsts(TopLevelDeclWithMembers c) { | ||
Contract.Requires(c != null); | ||
List<MemberDecl> newDecls = new List<MemberDecl>(); | ||
foreach (MemberDecl member in c.Members) { | ||
if (member is ConstantField constant) { | ||
if (!Attributes.Contains(constant.Attributes, "opaque")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See my comments in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't add it yet, but could add it to |
||
// Nothing to do | ||
} else if (!RefinementToken.IsInherited(constant.tok, c.EnclosingModuleDefinition)) { | ||
GenerateRevealLemma(constant, newDecls); | ||
} | ||
} | ||
} | ||
c.Members.AddRange(newDecls); | ||
} | ||
|
||
private void GenerateRevealLemma(ConstantField c, List<MemberDecl> newDecls) { | ||
// That is, given: | ||
// const {:opaque} foo := x | ||
// We produce: | ||
// lemma {:axiom} {:auto_generated} reveal_foo() | ||
// ensures foo == x | ||
|
||
Attributes lemma_attrs = new Attributes("auto_generated", new List<Expression>(), null); | ||
lemma_attrs = new Attributes("opaque_reveal", new List<Expression>(), lemma_attrs); | ||
lemma_attrs = new Attributes("verify", new List<Expression>() { new LiteralExpr(c.tok, false) }, lemma_attrs); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect something similar is done for opaque functions. Can the code be shared? |
||
var ens = new List<AttributedExpression>(); | ||
if (c.Rhs != null) { | ||
ens.Add(new AttributedExpression(new BinaryExpr(c.tok, BinaryExpr.Opcode.Eq, new NameSegment(c.Tok, c.Name, null), c.Rhs))); | ||
} | ||
var reveal = new Lemma(c.tok, "reveal_" + c.Name, c.HasStaticKeyword, new List<TypeParameter>(), | ||
new List<Formal>(), new List<Formal>(), new List<AttributedExpression>(), | ||
new Specification<FrameExpression>(new List<FrameExpression>(), null), ens, | ||
new Specification<Expression>(new List<Expression>(), null), null, lemma_attrs, null); | ||
newDecls.Add(reveal); | ||
reveal.InheritVisibility(c, true); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// For any function foo() with the :opaque attribute, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6575,7 +6575,9 @@ Bpl.Function GetReadonlyField(Field f) { | |
var cf = (ConstantField)f; | ||
if (cf.Rhs != null && RevealedInScope(cf)) { | ||
var etran = new ExpressionTranslator(this, predef, NewOneHeapExpr(f.tok)); | ||
sink.AddTopLevelDeclaration(ff.CreateDefinitionAxiom(etran.TrExpr(cf.Rhs))); | ||
if (!Attributes.Contains(cf.Attributes, "opaque")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Translator has a method |
||
sink.AddTopLevelDeclaration(ff.CreateDefinitionAxiom(etran.TrExpr(cf.Rhs))); | ||
} | ||
} | ||
sink.AddTopLevelDeclaration(ff); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// RUN: %dafny_0 /compile:0 "%s" > "%t" | ||
// RUN: %diff "%s.expect" "%t" | ||
|
||
module Module { | ||
module M { | ||
const {:opaque} Five := 5; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The semi-colon at the end of the line is not needed. |
||
} | ||
|
||
method Test() { | ||
assert M.Five == 5; // error: this is not known here | ||
reveal M.Five; | ||
assert M.Five == 5; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test might not have the effect you think. If you assert something twice, I expect Dafny to complain only about the first assert. This is because Dafny's semantics of Instead, I suggest that you create two paths through your method: if * {
assert M.Five == 5;
} else {
reveal M.Five;
assert M.Five == 5;
} or if
case true =>
assert M.Five == 5;
case true =>
reveal M.Five;
assert M.Five == 5; |
||
} | ||
} | ||
|
||
module Class { | ||
class Class { | ||
const {:opaque} Five := 5; | ||
} | ||
|
||
method Test(c: Class) { | ||
assert c.Five == 5; // error: this is not known here | ||
reveal c.Five; | ||
assert c.Five == 5; | ||
} | ||
} | ||
|
||
module TypeMembers { | ||
trait Tr { | ||
const fav: bool; | ||
const {:opaque} IsFavorite := fav; | ||
static const {:opaque} Five := 5; | ||
} | ||
|
||
datatype Color = Carrot | Pumpkin | ||
{ | ||
const {:opaque} IsFavorite := this == Pumpkin | ||
static const {:opaque} Five := 5; | ||
} | ||
|
||
newtype Small = x | 30 <= x < 40 witness 30 | ||
{ | ||
const {:opaque} IsFavorite := this == 34 | ||
static const {:opaque} Five := 5; | ||
} | ||
|
||
method Test(tr: Tr, c: Color, s: Small) { | ||
if | ||
case tr.IsFavorite => | ||
assert tr.fav; // error: this is not known here | ||
case c.IsFavorite => | ||
assert c == Pumpkin; // error: this is not known here | ||
case s.IsFavorite => | ||
assert s == 34; // error: this is not known here | ||
case tr.IsFavorite => | ||
reveal tr.IsFavorite; | ||
assert tr.fav; | ||
case c.IsFavorite => | ||
reveal c.IsFavorite; | ||
assert c == Pumpkin; | ||
case s.IsFavorite => | ||
reveal s.IsFavorite; | ||
assert s == 34; | ||
case true => | ||
if tr.IsFavorite && c.IsFavorite && s.IsFavorite { | ||
reveal tr.IsFavorite, c.IsFavorite, s.IsFavorite; | ||
assert !tr.fav || c == Carrot || s == 39; // error: not true | ||
} | ||
} | ||
|
||
function IsUneven(i: int): bool { i % 2 == 1 } | ||
|
||
method StaticTest() { | ||
if | ||
case IsUneven(Tr.Five) => | ||
assert Tr.Five == 5; // error: this is not known here | ||
case IsUneven(Color.Five) => | ||
assert Color.Five == 5; // error: this is not known here | ||
case IsUneven(Small.Five) => | ||
assert Color.Five == 5; // error: this is not known here | ||
case IsUneven(Tr.Five) => | ||
reveal Tr.Five; | ||
assert Tr.Five == 5; | ||
case IsUneven(Color.Five) => | ||
reveal Color.Five; | ||
assert Color.Five == 5; | ||
case IsUneven(Small.Five) => | ||
reveal Small.Five; | ||
assert Small.Five == 5; | ||
case true => | ||
if IsUneven(Tr.Five) && IsUneven(Color.Five) && IsUneven(Small.Five) { | ||
reveal Tr.Five, Color.Five, Small.Five; | ||
assert Tr.Five != 5 || Color.Five != 5 || Small.Five != 5; // error: not true | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
OpaqueConstants.dfy(10,18): Error: assertion might not hold | ||
OpaqueConstants.dfy(22,18): Error: assertion might not hold | ||
OpaqueConstants.dfy(50,16): Error: assertion might not hold | ||
OpaqueConstants.dfy(52,15): Error: assertion might not hold | ||
OpaqueConstants.dfy(54,15): Error: assertion might not hold | ||
OpaqueConstants.dfy(67,38): Error: assertion might not hold | ||
OpaqueConstants.dfy(76,21): Error: assertion might not hold | ||
OpaqueConstants.dfy(78,24): Error: assertion might not hold | ||
OpaqueConstants.dfy(80,24): Error: assertion might not hold | ||
OpaqueConstants.dfy(93,47): Error: assertion might not hold | ||
|
||
Dafny program verifier finished with 3 verified, 10 errors |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -753,19 +753,19 @@ Twostate-Resolution.dfy(445,20): Error: no label 'After' in scope at this time | |
Twostate-Resolution.dfy(452,17): Error: an @-label can only be applied to a two-state function | ||
Twostate-Resolution.dfy(453,14): Error: an @-label can only be applied to a two-state lemma | ||
Twostate-Resolution.dfy(459,19): Error: an @-label can only be applied to a two-state function | ||
Twostate-Resolution.dfy(481,11): Error: expression is not allowed to invoke a lemma (reveal_OrdinaryOpaque) | ||
Twostate-Resolution.dfy(481,11): Error: to reveal a function (OrdinaryOpaque), append parentheses | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test file ( |
||
Twostate-Resolution.dfy(482,26): Error: no label 'K' in scope at this time | ||
Twostate-Resolution.dfy(483,26): Error: an @-label can only be applied to a two-state lemma | ||
Twostate-Resolution.dfy(487,11): Error: expression is not allowed to invoke a lemma (reveal_OrdinaryOpaque) | ||
Twostate-Resolution.dfy(487,11): Error: to reveal a function (OrdinaryOpaque), append parentheses | ||
Twostate-Resolution.dfy(488,26): Error: no label 'K' in scope at this time | ||
Twostate-Resolution.dfy(495,11): Error: expression is not allowed to invoke a twostate lemma (reveal_Opaque) | ||
Twostate-Resolution.dfy(495,11): Error: to reveal a function (Opaque), append parentheses | ||
Twostate-Resolution.dfy(496,18): Error: no label 'K' in scope at this time | ||
Twostate-Resolution.dfy(497,19): Error: to reveal a two-state function, do not list any parameters or @-labels | ||
Twostate-Resolution.dfy(500,17): Error: a two-state function can only be revealed in a two-state context | ||
Twostate-Resolution.dfy(501,11): Error: expression is not allowed to invoke a twostate lemma (reveal_Opaque) | ||
Twostate-Resolution.dfy(501,11): Error: to reveal a function (Opaque), append parentheses | ||
Twostate-Resolution.dfy(502,18): Error: no label 'K' in scope at this time | ||
Twostate-Resolution.dfy(502,19): Error: a two-state function can only be revealed in a two-state context | ||
Twostate-Resolution.dfy(507,11): Error: expression is not allowed to invoke a twostate lemma (reveal_Opaque) | ||
Twostate-Resolution.dfy(507,11): Error: to reveal a function (Opaque), append parentheses | ||
Twostate-Resolution.dfy(508,18): Error: no label 'K' in scope at this time | ||
Twostate-Resolution.dfy(515,11): Error: two-state lemmas can only be used in two-state contexts | ||
Twostate-Resolution.dfy(535,22): Error: parameter 'a' of function P cannot be changed, compared to in the overridden function, from non-older to older | ||
|
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 suggest combining these two rewriters into one
OpaqueRewriter
.