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: Support for the {:opaque} attibute on const #2545

Merged
merged 17 commits into from
Aug 25, 2022
8 changes: 8 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Upcoming

- feat: Support for the `{:opaque}` attibute on `const`


# 3.8.0

- fix: Use the right bitvector comparison in decrease checks
Expand All @@ -22,12 +25,14 @@
- fix: Perform well-definedness checks for ensures clauses of forall statements (https://github.com/dafny-lang/dafny/pull/2606)
- fix: Resolution and verification of StmtExpr now pay attention to if the StmtExpr is inside an 'old' (https://github.com/dafny-lang/dafny/pull/2607)


# 3.7.3

- feat: *Less code navigation when verifying code*: In the IDE, failing postconditions and preconditions error messages now immediately display the sub-conditions that Dafny could not prove. Both on hover and in the Problems window. (https://github.com/dafny-lang/dafny/pull/2434)
- feat: Whitespaces and comments are kept in relevant parts of the AST (https://github.com/dafny-lang/dafny/pull/1801)
- fix: NuGet packages no longer depend on specific patch releases of the .NET frameworks.


# 3.7.2

- fix: Hovering over variables and methods inside cases of nested match statements work again. (https://github.com/dafny-lang/dafny/pull/2334)
Expand All @@ -39,11 +44,13 @@
- fix: Correctly infer type of numeric arguments, where the type is a subset type of a newtype. (https://github.com/dafny-lang/dafny/pull/2314)
- fix: Fix concurrency bug that sometimes led to an exception during the production of verification logs. (https://github.com/dafny-lang/dafny/pull/2398)


# 3.7.1

- fix: The Dafny runtime library for C# is now compatible with .NET Standard 2.1, as it was before 3.7.0. Its version has been updated to 1.2.0 to reflect this. (https://github.com/dafny-lang/dafny/pull/2277)
- fix: Methods produced by the test generation code can now be compiled directly to C# XUnit tests (https://github.com/dafny-lang/dafny/pull/2269)


# 3.7.0

- feat: The Dafny CLI, Dafny as a library, and the C# runtime are now available on NuGet. You can install the CLI with `dotnet tool install --global Dafny`. The library is available as `DafnyPipeline` and the runtime is available as `DafnyRuntime`. (https://github.com/dafny-lang/dafny/pull/2051)
Expand All @@ -57,6 +64,7 @@
- fix: Prevent the language server from crashing and not responding on resolution or ghost diagnostics errors (https://github.com/dafny-lang/dafny/pull/2080)
- fix: Various improvements to language server robustness (https://github.com/dafny-lang/dafny/pull/2254)


# 3.6.0

- feat: The `synthesize` attribute on methods with no body allows synthesizing objects based on method postconditions at compile time (currently only available for C#). See Section [22.2.20](https://dafny-lang.github.io/dafny/DafnyRef/DafnyRef#sec-synthesize-attr) of the Reference Manual. (https://github.com/dafny-lang/dafny/pull/1809)
Expand Down
16 changes: 16 additions & 0 deletions Source/Dafny/Resolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Copy link
Collaborator

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.

rewriters.Add(new AutoReqFunctionRewriter(this.reporter));
rewriters.Add(new TimeLimitRewriter(reporter));
Expand Down Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

For expr as NameSegment and expr as ExprDotName, you expect that expr will have those respective types. In other words, you expect that the as will not return null. To indicate this in the program text, I suggest you instead use a cast: (NameSegment)expr and (ExprDotName)expr.

}
MemberSelectExpr callee = (MemberSelectExpr)((ConcreteSyntaxExpression)expr).ResolvedExpression;
if (callee == null) {
} else if ((callee.Member is Lemma or TwoStateLemma && Attributes.Contains(callee.Member.Attributes, "axiom"))) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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);
}
Expand Down
49 changes: 49 additions & 0 deletions Source/Dafny/Rewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

See my comments in Translator.cs about having a method IsOpaque. I guess such a method can be defined in DafnyAst.cs or Resolver.cs instead of in Translator.cs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I didn't add it yet, but could add it to MemberDecl.

// 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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,
Expand Down
4 changes: 3 additions & 1 deletion Source/Dafny/Verifier/Translator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The Translator has a method IsOpaqueFunction, which checks if a function is opaque. I suggest you define an analogous method for constants and use it here. Alternatively, change the current IsOpaqueFunction to have the name IsOpaque and take any member declaration.

sink.AddTopLevelDeclaration(ff.CreateDefinitionAxiom(etran.TrExpr(cf.Rhs)));
}
}
sink.AddTopLevelDeclaration(ff);

Expand Down
2 changes: 1 addition & 1 deletion Test/allocated1/dafny0/OpaqueFunctions.dfy.expect
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ OpaqueFunctions.dfy(261,11): Error: assertion might not hold
OpaqueFunctions.dfy(326,16): Error: assertion might not hold
OpaqueFunctions.dfy(328,15): Error: assertion might not hold
OpaqueFunctions.dfy(330,15): Error: assertion might not hold
OpaqueFunctions.dfy(343,46): Error: result of operation might violate newtype constraint for 'Small'
OpaqueFunctions.dfy(343,38): Error: assertion might not hold
OpaqueFunctions.dfy(350,15): Error: assertion might not hold
OpaqueFunctions.dfy(352,15): Error: assertion might not hold
OpaqueFunctions.dfy(354,15): Error: assertion might not hold
Expand Down
96 changes: 96 additions & 0 deletions Test/dafny0/OpaqueConstants.dfy
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 assert allows it to implicitly assume the condition after the assert. So, if you remove the reveal, you probably will still get just one verification error.

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
}
}
}
12 changes: 12 additions & 0 deletions Test/dafny0/OpaqueConstants.dfy.expect
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
2 changes: 1 addition & 1 deletion Test/dafny0/OpaqueFunctions.dfy
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ module TypeMembers {
case true =>
if tr.IsFavorite() && c.IsFavorite() && s.IsFavorite() {
reveal tr.IsFavorite(), c.IsFavorite(), s.IsFavorite();
assert !tr.fav || c == Carrot || s == 55; // error: not true
assert !tr.fav || c == Carrot || s == 39; // error: not true
}
}

Expand Down
2 changes: 1 addition & 1 deletion Test/dafny0/OpaqueFunctions.dfy.expect
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ OpaqueFunctions.dfy(261,11): Error: assertion might not hold
OpaqueFunctions.dfy(326,16): Error: assertion might not hold
OpaqueFunctions.dfy(328,15): Error: assertion might not hold
OpaqueFunctions.dfy(330,15): Error: assertion might not hold
OpaqueFunctions.dfy(343,46): Error: result of operation might violate newtype constraint for 'Small'
OpaqueFunctions.dfy(343,38): Error: assertion might not hold
OpaqueFunctions.dfy(350,15): Error: assertion might not hold
OpaqueFunctions.dfy(352,15): Error: assertion might not hold
OpaqueFunctions.dfy(354,15): Error: assertion might not hold
Expand Down
10 changes: 5 additions & 5 deletions Test/dafny0/Twostate-Resolution.dfy.expect
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

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

The test file (Twostate-Resolution.dfy) still has comments like (admittedly, a poor error message). Please remove those now, since your error messages are good.

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
Expand Down